Current section

Files

Jump to
filtr lib filtr helpers.ex
Raw

lib/filtr/helpers.ex

defmodule Filtr.Helpers do
@moduledoc """
Helper functions for Filtr schema processing and plugin management.
"""
@supported_error_modes [:fallback, :strict, :raise]
@type_plugin_map_key :filtr_type_plugin_map
@default_error_mode Application.compile_env(:filtr, :error_mode) || :fallback
@doc """
Returns the default error mode configured for the application.
Reads from the application config key `:error_mode` under `:filtr`.
Falls back to `:fallback` if not configured.
"""
@spec default_error_mode() :: atom()
def default_error_mode, do: @default_error_mode
@spec supported_error_mode?(mode :: atom()) :: boolean()
def supported_error_mode?(mode) do
mode in @supported_error_modes
end
@spec supported_error_modes() :: [atom()]
def supported_error_modes do
@supported_error_modes
end
@doc """
Returns a map of type atoms to their corresponding plugin module.
This function builds and caches a mapping of all supported types to the
plugin that handle them. The map is cached in `:persistent_term` for
fast access across the application.
"""
@spec type_plugin_map() :: %{atom() => module()}
def type_plugin_map do
case :persistent_term.get(@type_plugin_map_key, nil) do
nil ->
map = build_type_plugin_map()
:persistent_term.put(@type_plugin_map_key, map)
map
map ->
map
end
end
defp build_type_plugin_map do
plugins = Filtr.Plugin.all()
Enum.reduce(plugins, %{}, fn plugin, type_map ->
types = plugin.types()
Enum.reduce(types, type_map, fn type, type_map ->
Map.put(type_map, type, plugin)
end)
end)
end
@run_opts_keys [:error_mode]
@doc """
Parses and structures parameter options into a standardized format.
Separates type and run options from validation options, grouping
validators under a `:validators` key.
Run options: #{Enum.join(@run_opts_keys, ", ")}
"""
@spec parse_param_opts(keyword()) :: keyword()
def parse_param_opts(opts) do
validators = Keyword.drop(opts, [:type] ++ @run_opts_keys)
opts
|> Keyword.take([:type] ++ @run_opts_keys)
|> Keyword.put(:validators, validators)
end
@doc """
Converts AST from nested `param` macro calls into a schema map.
This function recursively processes the AST generated by nested param blocks,
transforming them into a structured schema map suitable for validation.
## Examples
# Input AST from:
param :user do
param :name, :string, required: true
param :age, :integer, min: 18
end
# Output:
%{
user: %{
name: %{type: :string, validators: [required: true]},
age: %{type: :integer, validators: [min: 18]}
}
}
Only `param` macro calls are valid inside nested blocks. Other expressions
will raise an ArgumentError.
"""
@spec render_ast_to_schema(ast :: term()) :: map()
@spec render_ast_to_schema(ast :: term(), schema :: map()) :: map()
def render_ast_to_schema(ast, schema \\ %{})
def render_ast_to_schema({:param, _, [key, [do: ast]]}, schema) do
Map.put(schema, key, render_ast_to_schema(ast, %{}))
end
def render_ast_to_schema({:param, _, args}, schema) do
{key, type, opts} = extract_opts(args)
opts = Keyword.put(opts, :type, type)
Map.put(schema, key, parse_param_opts(opts))
end
def render_ast_to_schema({:__block__, _, items}, schema) when is_list(items) do
Enum.reduce(items, schema, fn item, acc_schema ->
render_ast_to_schema(item, acc_schema)
end)
end
def render_ast_to_schema(nil, schema), do: schema
def render_ast_to_schema(ast, _schema) do
raise ArgumentError, """
Invalid param definition in nested schema.
Expected:
- `param :name, :type`
- `param :name, :type, opts`
- `param :name do ... end`
- `param :name, opts do ... end`
- `param :name, :list do ... end`
- `param :name, :list, opts do ... end`
Got: `#{Macro.to_string(ast)}`
Only `param` macro calls are allowed inside nested param blocks.
"""
end
defp extract_opts([key, type]) do
{key, type, []}
end
defp extract_opts([key, type, opts]) do
{key, type, opts}
end
end