defmodule Formular.Client.Config do
@moduledoc """
Configuration for a Formular client.
The following keys are currently supported:
- `client_name`
- the identity of the client, can be any string. Default `nil`
- `url`
- the url of the remote server, default `nil`
- `read_timeout`
- a timeout setting for waiting for all the formulas
- `formulas`
- formula definition, check "Formula Configuration" section for more
information.
- `compiler`
- MFA config for compiling the string into Elixir code
- `adapter`
- an adapter for getting the data from the remote server
## Formula Configuration
A fomular is configured as a two-element tuple:
```elixir
{"my-formula", compile_as: MyFm, context: My.ContextModule}
```
Here `MyFm` is the name of module for the code to be compiled into;
`"my-formula"` is the name of the formula on the server;
`My.ContextModule` is the helper module that can be used in the
code.
```
A single formula name string is also accepted:
```elixir
[
...
formulas: ["my-formula"]
]
```
In this case, `compile_as` and `context` are optional and treated as
`nil`s.
"""
alias __MODULE__
require Logger
@type formula_name() :: String.t()
@type formula_def() :: {formula_name(), compile_options()}
@type compile_options() :: [compile_option()]
@type compile_option() ::
{:compile_as, module()} | {:context, module()} | {:allow_modules, [module()]}
@type formula_full_def() ::
formula_def()
| {module(), name :: String.t(), module() | nil}
| {module(), name :: String.t()}
@type compile_function ::
({name :: String.t(), code :: binary(), module(), nil | module()} ->
:ok | {:error, term()})
@type t :: %Config{
client_name: String.t(),
url: String.t(),
read_timeout: :infinity | non_neg_integer(),
formulas: [formula_def()],
compiler: {module(), atom(), args :: list()} | compile_function(),
adapter: {module(), keyword()}
}
defstruct [
:client_name,
:url,
formulas: [],
read_timeout: 10_000,
compiler: {Formular.Client.Compiler, :compile, []},
adapter: {Formular.Client.Adapter.Websocket, []}
]
@doc """
Build a new config constructure.
"""
@spec new(Enum.t()) :: t()
def new(opts) do
struct!(Config, opts)
|> format_formulas()
end
defp format_formulas(config) do
config
|> Map.update!(
:formulas,
&Enum.map(&1 || [], fn
name when is_binary(name) ->
{name, []}
{mod, name} when is_atom(mod) and is_binary(name) ->
Logger.warning(
"This format ({module, formula_name}) of formula Configuration is deprecated, please use `{formula_name, compile_as: module}` format."
)
{name, compile_as: mod}
{name, opts} when is_binary(name) and is_list(opts) ->
{name, opts}
{mod, name, context} when is_atom(mod) and is_binary(name) and is_atom(context) ->
Logger.warning(
"This format ({module, formula_name, context}) of formula Configuration is deprecated, please use `{formula_name, compile_as: module, context: context}` format."
)
{name, compile_as: mod, context: context}
end)
)
end
@doc """
Get the config for a given formula name.
"""
@spec formula_config(t(), formula_name()) :: formula_def() | nil
def formula_config(%Config{formulas: formulas}, name) when is_list(formulas) do
Enum.find(formulas, fn
{^name, _opts} ->
true
_ ->
false
end)
end
end