Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 21 additions & 53 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
[![Hex Version](https://img.shields.io/hexpm/v/numscriptex.svg)](https://hex.pm/packages/numscriptex)
[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/numscriptex/)

NumscriptEx is a library that allows its users to check and run Numscripts in Elixir. If this is your first time hearing about
NumscriptEx is a library that allows its users to run Numscripts in Elixir. If this is your first time hearing about
Numscripts, here is a quick explanation:

[Numscript](https://docs.formance.com/numscript/) is a DSL made by [Formance](https://www.formance.com/)
Expand Down Expand Up @@ -34,14 +34,13 @@ Ex:
```elixir
config :numscriptex,
binary_path: :numscriptex |> :code.priv_dir() |> Path.join("numscript.wasm")
version: "0.0.2",
version: "0.1.0",
retries: 3
```
These above are the default values.

## Usage
You can build Numscripts dynamically with `Numscriptex.Builder.build/1`, check if your script is valid with `Numscriptex.check/1`,
and last but not least, you can run your script with `Numscriptex.run/2`.
You can build Numscripts dynamically with `Numscriptex.Builder.build/1` and run your script with `Numscriptex.run/2`.

You can read more about the `Numscriptex.Builder` module and how to use it on its [guide](https://hexdocs.pm/numscriptex/builder-introduction.html)

Expand All @@ -59,18 +58,30 @@ The abstraction is made by creating a struct:
iex> %Numscriptex.Run{
...> balances: %{},
...> metadata: %{},
...> variables: %{}
...> variables: %{},
...> feature_flags: []
...> }
```
Where:
- `:balances` a map with the account's assets balances.
- `:metadata` [metada variables](https://docs.formance.com/numscript/reference/metadata);
- `:variables` [variables](https://docs.formance.com/numscript/reference/variables) used inside the script.
- `:metadata` [metada variables](https://docs.formance.com/modules/numscript/reference/metadata);
- `:variables` [variables](https://docs.formance.com/modules/numscript/reference/variables).
- `:feature_flags` feature flags that enables numscript experimental features.

The avaialable feature flags are:
- experimental_overdraft_function
- experimental_get_asset_function
- experimental_get_amount_function
- experimental_oneof
- experimental_account_interpolation
- experimental_mid_script_function_call
- experimental_asset_colors

And to create a new struct, you can use the `Numscriptex.Run.put/3` or `Numscriptex.Run.put!/3` functions. Ex:
```elixir
iex> variables = %{"order" => "orders:2345"}
...> balances = %{"orders:2345" => %{"USD/2" => 1000}}
...> feature_flags = [:experimental_oneof]
...> metadata = %{
...> "merchants:1234" => %{"commission" => "15%"},
...> "orders:2345" => %{"merchant" => "merchants:1234"}
Expand All @@ -80,6 +91,7 @@ iex> variables = %{"order" => "orders:2345"}
...> |> Numscriptex.Run.put!(:balances, balances)
...> |> Numscriptex.Run.put!(:metadata, metadata)
...> |> Numscriptex.Run.put!(:variables, variables)
...> |> Numscriptex.Run.put!(:feature_flags, feature_flags)
```

Will return:
Expand All @@ -88,6 +100,7 @@ Will return:
iex> %Numscriptex.Run{
...> variables: %{"orders:2345" => %{"USD/2" => 1000}},
...> balances: %{"order" => "orders:2345"},
...> feature_flags: [:experimental_oneof]
...> metadata: %{
...> "merchants:1234" => %{"commission" => "15%"},
...> "orders:2345" => %{"merchant" => "merchants:1234"}
Expand All @@ -97,53 +110,8 @@ iex> %Numscriptex.Run{

Kindly reminder: you will always need a valid `Numscriptex.Run` struct to successfully execute your scripts.

### Check
To use `Numscriptex.check/1` you just have to pass your numscript as it's argument. Ex:

```elixir
iex> "tmp/script.num"
...> |> File.read!()
...> |> Numscriptex.check()
{:ok, %{script: script}
```

You don´t need to necessarily read from a file, as long as it is a string it's fine.

Sometimes, even if your script is valid, it could also return some warnings, infos or hints inside the map.
Ex:
```elixir
iex> {:ok, %{
...> script: "your numscript here",
...> warnings: [
...> %CheckLog{
...> character: 10,
...> level: :warning,
...> line: 1,
...> message: "warning message"
...> }
...> ],
...> hints: [
...> %CheckLog{
...> character: 2,
...> level: :hint,
...> line: 7,
...> message: "hint message"
...> }
...> ],
...> infos: [
...> %CheckLog{
...> character: 9,
...> level: :info,
...> line: 14,
...> message: "info message"
...> }
...> ]
...> }
...> }
```
The `:script` is the only field that will always return if your script is valid, the other three are optional.
### Run
To use `Numscriptex.run/2` your first argument must be your script (the same you used in `Numscriptex.check/1`), and the second must be the `%Numscriptex.Run{}` struct. Ex:
To use `Numscriptex.run/2` you must pass your script as the first argument, and the `%Numscriptex.Run{}` struct as the second. Ex:

```elixir
iex> Numscriptex.run(script, struct)
Expand Down
87 changes: 5 additions & 82 deletions lib/numscriptex.ex
Original file line number Diff line number Diff line change
@@ -1,29 +1,19 @@
defmodule Numscriptex do
@moduledoc """
NumscriptEx is a library that allows its users to check and run [Numscripts](https://docs.formance.com/numscript/)
NumscriptEx is a library that allows its users to run [Numscripts](https://docs.formance.com/numscript/)
via Elixir.

Want to check if your script is valid and ready to go? Use the `check/1` function.
Already checked the script and want to execute him? Use the `run/2` function.
"""

alias Numscriptex.AssetsManager
alias Numscriptex.Balance
alias Numscriptex.CheckLog
alias Numscriptex.Posting
alias Numscriptex.Run
alias Numscriptex.Utilities

require AssetsManager

AssetsManager.ensure_wasm_binary_is_valid()

@type check_result() :: %{
required(:script) => binary(),
optional(:hints) => list(CheckLog.t()),
optional(:infos) => list(CheckLog.t()),
optional(:warnings) => list(CheckLog.t())
}

@type run_result() :: %{
required(:balances) => list(Balance.t()),
required(:postings) => list(Posting.t()),
Expand All @@ -32,7 +22,7 @@ defmodule Numscriptex do
}

@type errors() :: %{
required(:reason) => list(CheckLog.t()) | any(),
required(:reason) => any(),
optional(:details) => any()
}

Expand All @@ -41,7 +31,7 @@ defmodule Numscriptex do

```elixir
iex> Numscriptex.version()
%{numscriptex: "v0.2.5", numscript_wasm: "v0.0.2"}
%{numscriptex: "v0.2.5", numscript_wasm: "v0.1.0"}
```
"""
@spec version() :: %{numscriptex: binary(), numscript_wasm: binary()}
Expand All @@ -61,32 +51,6 @@ defmodule Numscriptex do
end
end

@doc """
To use `check/1` you just need to pass your numscript as its argument.
Ex:

```elixir
iex> script = "send [USD/2 100] (source = @foo destination = @bar)"
iex> Numscriptex.check(script)
{:ok, %{script: script}}
```

It could also return some warnings, infos or hints inside the map
"""
@spec check(binary()) :: {:ok, check_result()} | {:error, errors()}
def check(input) do
case execute_command(input, :check) do
:ok ->
{:ok, %{script: input}}

{:ok, details} ->
{:ok, %{script: input, details: normalize_check_logs(details)}}

{:error, %{reason: errors}} ->
{:error, %{reason: normalize_check_logs(errors)}}
end
end

@doc """
To use `run/2` your first argument must be your script, and the second must
be a `%Numscriptex.Run{}` (go to `Numscriptex.Run` module to see more) struct.
Expand All @@ -110,7 +74,7 @@ defmodule Numscriptex do
initial_balance = Map.get(run_struct, :balances)

run_struct
|> Map.from_struct()
|> Run.normalize_to_map()
|> Map.merge(%{script: numscript})
|> JSON.encode!()
|> execute_command(:run)
Expand Down Expand Up @@ -230,20 +194,6 @@ defmodule Numscriptex do
{:ok, %{numscript_wasm: String.trim(result)}}
end

defp handle_operation_result({:ok, %{"valid" => valid?} = result}, :check)
when is_boolean(valid?) and valid? do
normalized_result = Map.delete(result, "valid")

has_details? = not Enum.empty?(normalized_result)

if has_details?, do: {:ok, normalized_result}, else: :ok
end

defp handle_operation_result({:ok, %{"valid" => valid?, "errors" => _err} = result}, :check)
when is_boolean(valid?) and not valid? do
{:error, %{reason: Map.delete(result, "valid")}}
end

defp handle_operation_result({:ok, %{"postings" => postings} = result}, :run) do
if Enum.empty?(postings),
do: {:error, %{reason: :invalid_input}},
Expand All @@ -256,8 +206,6 @@ defmodule Numscriptex do

defp handle_errors({:ok, _} = result), do: result

defp handle_errors(:ok), do: :ok

defp handle_errors({:error, %{reason: reason, details: details}}) do
{:error, %{reason: normalize_error(reason), details: normalize_error(details)}}
end
Expand All @@ -273,29 +221,4 @@ defmodule Numscriptex do
end

defp normalize_error(error), do: error

defp normalize_check_logs(logs) do
logs
|> Utilities.normalize_keys(:atom)
|> check_log_level_to_atom()
|> check_logs_to_struct()
|> Enum.into(%{})
end

defp check_logs_to_struct(logs) do
Enum.map(logs, fn {key, value} ->
{key, Enum.map(value, &CheckLog.from_map/1)}
end)
end

defp check_log_level_to_atom(check_logs) do
Enum.flat_map(check_logs, fn {key, logs} ->
normalized_level_field =
Enum.map(logs, fn log ->
Map.update(log, :level, nil, &String.to_existing_atom/1)
end)

Map.replace(check_logs, key, normalized_level_field)
end)
end
end
2 changes: 1 addition & 1 deletion lib/numscriptex/assets_manager.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ defmodule Numscriptex.AssetsManager do
"""

Module.register_attribute(__MODULE__, :numscript_wasm_version, persist: true)
@numscript_wasm_version Application.compile_env(:numscriptex, :version, "0.0.2")
@numscript_wasm_version Application.compile_env(:numscriptex, :version, "0.1.0")
@retries Application.compile_env(:numscriptex, :retries, 3)
@numscript_checksums_url "https://github.com/PagoPlus/numscript-wasm/releases/download/v#{@numscript_wasm_version}/numscript_checksums.txt"
@numscript_wasm_url "https://github.com/PagoPlus/numscript-wasm/releases/download/v#{@numscript_wasm_version}/numscript.wasm"
Expand Down
74 changes: 0 additions & 74 deletions lib/numscriptex/check_log.ex

This file was deleted.

Loading