defmodule AshJido.Schema do
@moduledoc false
alias AshJido.TypeMapper
alias Spark.Dsl.Transformer
@default_belongs_to_type Application.compile_env(:ash, :default_belongs_to_type, :uuid)
@doc false
@spec build_parameter_schema(term(), AshJido.Resource.JidoAction.t(), Spark.Dsl.t()) ::
keyword()
def build_parameter_schema(ash_action, jido_action, dsl_state) do
case ash_action.type do
:create ->
accepted_attrs =
accepted_attributes_to_schema(ash_action, dsl_state, jido_action)
action_args = action_args_to_schema(ash_action.arguments || [], jido_action)
accepted_attrs ++ action_args
:update ->
base = primary_key_to_schema(dsl_state, :update)
accepted_attrs =
accepted_attributes_to_schema(ash_action, dsl_state, jido_action)
action_args = action_args_to_schema(ash_action.arguments || [], jido_action)
base ++ accepted_attrs ++ action_args
:destroy ->
base = primary_key_to_schema(dsl_state, :destroy)
action_args = action_args_to_schema(ash_action.arguments || [], jido_action)
base ++ action_args
_ ->
base_schema = action_args_to_schema(ash_action.arguments || [], jido_action)
if ash_action.type == :read and jido_action.query_params? do
base_schema ++ AshJido.QueryParams.schema(jido_action)
else
base_schema
end
end
end
@doc false
@spec primary_key_fields(Spark.Dsl.t()) :: [atom()]
def primary_key_fields(dsl_state) do
dsl_state
|> Transformer.get_entities([:attributes])
|> Enum.filter(& &1.primary_key?)
|> Enum.map(& &1.name)
end
defp accepted_attributes_to_schema(ash_action, dsl_state, jido_action) do
accepted_names = ash_action.accept || []
attributes_by_name = attributes_by_name(dsl_state)
belongs_to_source_attributes = belongs_to_source_attributes(dsl_state)
accepted_names
|> Enum.flat_map(fn attr_name ->
attr = Map.get(attributes_by_name, attr_name)
relationship = Map.get(belongs_to_source_attributes, attr_name)
cond do
attr && include_schema_input?(attr, jido_action) ->
[{attr_name, attribute_to_nimble_options(attr)}]
attr ->
[]
relationship && include_source_attribute_schema_input?(relationship, jido_action) ->
[{attr_name, relationship_source_attribute_to_nimble_options(relationship)}]
true ->
[]
end
end)
end
defp attributes_by_name(dsl_state) do
dsl_state
|> Transformer.get_entities([:attributes])
|> Map.new(&{&1.name, &1})
end
defp belongs_to_source_attributes(dsl_state) do
dsl_state
|> Transformer.get_entities([:relationships])
|> Enum.filter(&(&1.type == :belongs_to))
|> Map.new(fn relationship ->
{relationship.source_attribute || :"#{relationship.name}_id", relationship}
end)
end
defp primary_key_to_schema(dsl_state, action_type) do
primary_key = primary_key_fields(dsl_state)
attributes_by_name = attributes_by_name(dsl_state)
Enum.map(primary_key, fn attr_name ->
attr = Map.get(attributes_by_name, attr_name)
opts =
case attr do
nil -> [type: :any]
attr -> attribute_to_nimble_options(attr)
end
opts =
opts
|> Keyword.put(:required, true)
|> Keyword.put(:doc, primary_key_doc(attr_name, action_type))
{attr_name, opts}
end)
end
defp primary_key_doc(attr_name, action_type) do
action = action_type |> Atom.to_string() |> String.downcase()
"Primary key field #{attr_name} of record to #{action}"
end
defp attribute_to_nimble_options(attr) do
base_type = TypeMapper.map_ash_type(attr.type)
opts = [type: base_type]
opts =
if attr.allow_nil? == false and is_nil(attr.default) do
Keyword.put(opts, :required, true)
else
opts
end
case attr.description do
desc when is_binary(desc) -> Keyword.put(opts, :doc, desc)
_ -> opts
end
end
defp relationship_source_attribute_to_nimble_options(relationship) do
allow_nil? =
if relationship.primary_key? do
false
else
relationship.allow_nil?
end
TypeMapper.ash_type_to_nimble_options(
relationship.attribute_type || @default_belongs_to_type,
%{allow_nil?: allow_nil?}
)
end
defp action_args_to_schema(arguments, jido_action) do
arguments
|> Enum.filter(&include_schema_input?(&1, jido_action))
|> Enum.map(fn arg ->
{arg.name, TypeMapper.ash_type_to_nimble_options(arg.type, arg)}
end)
end
defp include_schema_input?(_input, %{include_private?: true}), do: true
defp include_schema_input?(%{public?: false}, _jido_action), do: false
defp include_schema_input?(_input, _jido_action), do: true
defp include_source_attribute_schema_input?(_relationship, %{include_private?: true}), do: true
defp include_source_attribute_schema_input?(%{attribute_public?: false}, _jido_action),
do: false
defp include_source_attribute_schema_input?(%{attribute_public?: true}, _jido_action), do: true
defp include_source_attribute_schema_input?(relationship, jido_action),
do: include_schema_input?(relationship, jido_action)
end