defmodule AshPhoenix.FilterForm.Arguments do
@moduledoc "Represents the arguments to a calculation being filtered on"
defstruct [:input, :params, :arguments, errors: []]
def new(params, []) do
%__MODULE__{input: %{}, params: params, arguments: [], errors: []}
end
def new(params, arguments) do
{input, errors} = validate_arguments(arguments, params)
%__MODULE__{input: input, params: params, arguments: arguments, errors: errors}
end
def validate_arguments(arguments, params) do
Enum.reduce(arguments, {%{}, []}, fn argument, {arg_values, errors} ->
value =
default(
Map.get(params, argument.name, Map.get(params, to_string(argument.name))),
argument.default
)
cond do
Ash.Filter.TemplateHelpers.expr?(value) && argument.allow_expr? ->
{Map.put(arg_values, argument.name, nil), errors}
Ash.Filter.TemplateHelpers.expr?(value) ->
{arg_values, ["Argument #{argument.name} does not support expressions!" | errors]}
is_nil(value) && argument.allow_nil? ->
{Map.put(arg_values, argument.name, nil), errors}
is_nil(value) ->
{arg_values, ["Argument #{argument.name} is required" | errors]}
!Map.get(params, argument.name, Map.get(params, to_string(argument.name))) && value ->
{Map.put(arg_values, argument.name, value), errors}
true ->
with {:ok, casted} <- Ash.Type.cast_input(argument.type, value, argument.constraints),
{:ok, casted} <-
Ash.Type.apply_constraints(argument.type, casted, argument.constraints) do
{Map.put(arg_values, argument.name, casted), errors}
else
{:error, error} ->
{arg_values, [error | errors]}
end
end
end)
end
defp default(nil, {module, function, args}), do: apply(module, function, args)
defp default(nil, value) when is_function(value, 0), do: value.()
defp default(nil, value), do: value
defp default(value, _), do: value
def errors(arguments, transform_errors) do
arguments.errors
|> Enum.filter(fn
error when is_exception(error) ->
AshPhoenix.FormData.Error.impl_for(error)
{_key, _value, _vars} ->
true
_ ->
false
end)
|> Enum.flat_map(
&AshPhoenix.FormData.Helpers.transform_arguments_error(
arguments,
&1,
transform_errors
)
)
|> Enum.map(fn
{field, message, vars} ->
vars =
vars
|> List.wrap()
|> Enum.flat_map(fn {key, value} ->
try do
if is_integer(value) do
[{key, value}]
else
[{key, to_string(value)}]
end
rescue
_ ->
[]
end
end)
{field, {message || "", vars}}
end)
end
defimpl Phoenix.HTML.FormData do
@impl true
def to_form(arguments, opts) do
errors = AshPhoenix.FilterForm.Arguments.errors(arguments, opts[:transform_errors])
%Phoenix.HTML.Form{
source: arguments,
impl: __MODULE__,
id: opts[:id],
name: opts[:as],
errors: errors,
data: arguments,
params: arguments.params,
hidden: [],
options: Keyword.put_new(opts, :method, "GET")
}
end
@impl true
def input_type(_, _, _), do: :text_input
@impl true
def to_form(form, phoenix_form, :arguments, _opts) do
[
Phoenix.HTML.Form.form_for(form.source.arguments, "arguments",
transform_errors: form.transform_errors,
as: phoenix_form.name <> "[arguments]"
)
]
end
def to_form(_, _, other, _) do
raise "Invalid inputs_for name #{other}. Only :arguments is supported"
end
@impl true
def input_value(arguments, _, name) do
if Enum.find(arguments.arguments, &(&1.name == name)) do
Map.get(
arguments.input,
name,
Map.get(arguments.params, name, Map.get(arguments.params, to_string(name)))
)
else
raise "Invalid filter form field #{name}. Only #{Enum.map_join(arguments.arguments, ", ", &inspect(&1.name))} are supported."
end
end
@impl true
def input_validations(_, _, _), do: []
end
end