Packages
ash
3.5.13
3.29.3
3.29.2
3.29.1
3.29.0
3.28.0
3.27.8
3.27.7
3.27.6
3.27.5
3.27.4
3.27.3
3.27.2
3.27.1
3.27.0
3.26.0
3.25.2
3.25.1
3.25.0
retired
3.24.7
3.24.6
3.24.5
3.24.4
3.24.3
3.24.2
3.24.1
3.24.0
3.23.1
3.23.0
3.22.2
3.22.1
3.22.0
3.21.3
3.21.2
3.21.1
3.21.0
3.20.0
3.19.3
3.19.2
3.19.1
3.19.0
3.18.0
3.17.1
3.17.0
3.16.0
3.15.0
3.14.1
3.14.0
retired
3.13.2
3.13.1
3.13.0
3.12.0
3.11.3
3.11.2
3.11.1
3.11.0
3.10.1
3.10.0
3.9.0
3.8.0
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
retired
3.6.3
retired
3.6.2
3.6.1
3.6.0
3.5.43
3.5.42
3.5.41
3.5.40
3.5.39
3.5.38
3.5.37
3.5.36
3.5.35
3.5.34
3.5.33
3.5.32
3.5.31
3.5.30
3.5.29
3.5.28
3.5.27
3.5.26
3.5.25
3.5.24
3.5.23
3.5.22
3.5.21
3.5.20
3.5.19
3.5.18
3.5.17
3.5.16
3.5.15
3.5.14
3.5.13
3.5.12
3.5.11
3.5.10
3.5.9
3.5.8
3.5.7
3.5.6
3.5.5
3.5.4
3.5.3
3.5.2
3.5.1
3.5.0
3.4.74
retired
3.4.73
3.4.72
3.4.71
3.4.70
3.4.69
3.4.68
3.4.67
3.4.66
3.4.65
3.4.64
3.4.63
3.4.62
3.4.61
3.4.60
3.4.59
3.4.58
3.4.57
3.4.56
3.4.55
3.4.54
3.4.53
3.4.52
3.4.51
3.4.50
3.4.49
3.4.48
3.4.47
3.4.46
3.4.45
3.4.44
3.4.43
3.4.42
3.4.41
3.4.40
3.4.39
3.4.38
3.4.37
3.4.36
3.4.35
3.4.34
3.4.33
3.4.32
3.4.31
3.4.30
3.4.29
3.4.28
3.4.27
3.4.26
3.4.25
3.4.24
3.4.23
3.4.22
3.4.21
3.4.20
3.4.19
3.4.18
3.4.17
3.4.16
3.4.15
3.4.14
3.4.13
3.4.12
3.4.11
3.4.10
3.4.9
3.4.8
3.4.7
3.4.6
3.4.5
3.4.4
3.4.3
3.4.2
3.4.1
3.4.0
3.3.3
3.3.2
3.3.1
3.3.0
3.2.6
3.2.5
3.2.4
3.2.3
3.2.2
3.2.1
3.2.0
3.1.8
3.1.7
3.1.6
3.1.5
3.1.4
3.1.3
3.1.2
3.1.1
3.1.0
3.0.16
3.0.15
3.0.14
3.0.13
3.0.12
3.0.11
3.0.10
3.0.9
3.0.8
3.0.7
3.0.6
3.0.5
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
3.0.0-rc.46
3.0.0-rc.45
3.0.0-rc.44
3.0.0-rc.43
3.0.0-rc.42
3.0.0-rc.41
3.0.0-rc.40
3.0.0-rc.39
3.0.0-rc.38
3.0.0-rc.37
3.0.0-rc.36
3.0.0-rc.35
3.0.0-rc.34
3.0.0-rc.33
3.0.0-rc.32
3.0.0-rc.31
3.0.0-rc.29
3.0.0-rc.28
3.0.0-rc.27
3.0.0-rc.26
3.0.0-rc.25
3.0.0-rc.24
3.0.0-rc.23
3.0.0-rc.22
3.0.0-rc.21
3.0.0-rc.20
3.0.0-rc.19
3.0.0-rc.18
3.0.0-rc.17
3.0.0-rc.16
3.0.0-rc.15
3.0.0-rc.14
3.0.0-rc.13
3.0.0-rc.12
3.0.0-rc.11
3.0.0-rc.10
3.0.0-rc.9
3.0.0-rc.8
3.0.0-rc.7
3.0.0-rc.6
3.0.0-rc.5
3.0.0-rc.4
3.0.0-rc.3
3.0.0-rc.1
3.0.0-rc.0
2.21.15
2.21.14
2.21.13
2.21.12
2.21.11
2.21.10
2.21.9
2.21.8
2.21.7
2.21.6
2.21.5
2.21.4
2.21.3
2.21.2
2.21.1
2.21.0
2.20.3
2.20.2
2.20.1
2.20.0
2.19.14
2.19.13
2.19.12
2.19.11
2.19.10
2.19.9
2.19.8
2.19.7
2.19.6
2.19.5
2.19.4
2.19.3
retired
2.19.2
retired
2.19.1
retired
2.19.0
retired
2.18.2
2.18.1
2.18.0
2.17.24
2.17.23
2.17.22
2.17.21
2.17.20
2.17.19
2.17.18
2.17.17
2.17.16
2.17.15
2.17.14
2.17.13
2.17.12
2.17.11
2.17.10
2.17.9
2.17.8
2.17.7
2.17.6
2.17.5
2.17.4
2.17.3
2.17.2
2.17.1
2.17.0
2.16.1
2.16.0
2.15.20
2.15.19
2.15.18
2.15.17
2.15.16
2.15.15
2.15.14
2.15.13
2.15.12
2.15.11
2.15.10
2.15.9
2.15.8
2.15.7
2.15.6
2.15.5
2.15.4
2.15.2
2.15.1
2.15.0
2.14.21
2.14.20
2.14.19
2.14.18
2.14.17
2.14.16
2.14.15
2.14.14
2.14.13
2.14.12
2.14.11
2.14.10
2.14.9
2.14.8
2.14.7
2.14.6
2.14.5
2.14.4
2.14.3
2.14.2
2.14.1
2.14.0
2.13.4
retired
2.13.3
2.13.2
2.13.1
2.13.0
2.12.1
2.12.0
2.11.11
2.11.10
2.11.9
2.11.8
2.11.7
2.11.6
2.11.5
2.11.4
2.11.3
2.11.2
2.11.1
2.11.0
2.11.0-rc.3
2.11.0-rc.2
2.11.0-rc.1
2.11.0-rc.0
2.10.2
2.10.1
2.10.0
2.9.29
2.9.28
2.9.27
2.9.26
2.9.25
2.9.24
2.9.23
2.9.22
2.9.21
2.9.20
2.9.19
2.9.18
2.9.17
2.9.16
2.9.15
2.9.14
2.9.13
2.9.12
2.9.11
2.9.10
2.9.9
2.9.8
2.9.7
2.9.6
2.9.5
2.9.4
2.9.3
2.9.2
2.9.1
2.9.0
2.8.1
2.8.0
2.7.1
2.7.0
2.6.31
2.6.30
2.6.29
2.6.28
2.6.27
2.6.26
2.6.25
2.6.24
2.6.23
2.6.22
2.6.21
2.6.20
2.6.19
2.6.18
2.6.17
2.6.16
2.6.15
2.6.14
2.6.13
2.6.11
2.6.10
2.6.9
2.6.8
2.6.7
2.6.6
2.6.5
2.6.4
2.6.3
2.6.2
2.6.1
2.6.0
2.5.16
2.5.15
2.5.14
2.5.13
2.5.12
2.5.11
2.5.10
2.5.9
2.5.8
2.5.7
2.5.6
2.5.5
2.5.4
2.5.3
2.5.2
2.5.1
2.5.0
2.5.0-rc.6
2.5.0-rc.5
2.5.0-rc.4
2.5.0-rc.3
2.5.0-rc.2
2.5.0-rc.1
2.5.0-rc.0
2.4.30
2.4.29
2.4.28
2.4.27
2.4.26
2.4.25
2.4.24
2.4.23
2.4.22
2.4.21
2.4.20
2.4.19
2.4.18
2.4.17
2.4.16
2.4.15
2.4.14
2.4.13
2.4.12
2.4.11
2.4.10
2.4.9
2.4.8
2.4.7
2.4.6
2.4.5
2.4.4
2.4.3
2.4.2
2.4.1
2.4.0
2.3.0
2.2.0
2.1.0
2.0.0
2.0.0-rc.15
2.0.0-rc.14
2.0.0-rc.13
2.0.0-rc.12
2.0.0-rc.11
2.0.0-rc.10
2.0.0-rc.9
2.0.0-rc.8
2.0.0-rc.7
2.0.0-rc.6
2.0.0-rc.5
2.0.0-rc.4
2.0.0-rc.3
2.0.0-rc.2
2.0.0-rc.1
2.0.0-rc.0
2.0.0-pre.8
2.0.0-pre.7
2.0.0-pre.6
2.0.0-pre.5
2.0.0-pre.4
2.0.0-pre.3
2.0.0-pre.2
2.0.0-pre.1
2.0.0-pre.0
1.53.3
1.53.2
1.53.0
1.52.0-rc.22
1.52.0-rc.21
1.52.0-rc.20
1.52.0-rc.19
1.52.0-rc.18
1.52.0-rc.17
1.52.0-rc.16
1.52.0-rc.15
1.52.0-rc.14
1.52.0-rc.13
1.52.0-rc.12
1.52.0-rc.11
1.52.0-rc.10
1.52.0-rc.9
1.52.0-rc.8
1.52.0-rc.7
1.52.0-rc.6
1.52.0-rc.5
1.52.0-rc.4
1.52.0-rc.3
1.52.0-rc.2
1.52.0-rc.1
1.52.0-rc.0
1.51.2
1.51.1
retired
1.51.0
1.50.21
1.50.20
1.50.19
1.50.18
1.50.17
1.50.16
1.50.15
1.50.14
1.50.13
1.50.12
1.50.11
1.50.10
1.50.9
1.50.8
1.50.7
1.50.6
1.50.5
1.50.4
1.50.3
1.50.2
1.50.1
1.50.0
1.49.0
1.48.0-rc.30
1.48.0-rc.29
1.48.0-rc.28
1.48.0-rc.27
1.48.0-rc.26
1.48.0-rc.25
1.48.0-rc.24
1.48.0-rc.23
1.48.0-rc.22
1.48.0-rc.21
1.48.0-rc.20
1.48.0-rc.19
1.48.0-rc.18
1.48.0-rc.17
1.48.0-rc.16
1.48.0-rc.15
1.48.0-rc.14
1.48.0-rc.13
1.48.0-rc.12
1.48.0-rc.11
1.48.0-rc.10
1.48.0-rc.9
1.48.0-rc.8
1.48.0-rc.7
1.48.0-rc.6
1.48.0-rc.5
1.48.0-rc.4
1.48.0-rc.3
1.48.0-rc.2
1.48.0-rc.1
1.48.0-rc.0
1.47.12
1.47.11
1.47.10
1.47.9
1.47.8
1.47.7
1.47.6
1.47.5
1.47.4
1.47.3
1.47.2
1.47.1
1.47.0
1.46.13
1.46.12
1.46.11
1.46.10
1.46.9
1.46.8
1.46.7
1.46.6
1.46.5
1.46.4
1.46.3
1.46.2
1.46.1
1.46.0
1.45.0-rc9
1.45.0-rc8
1.45.0-rc7
1.45.0-rc6
1.45.0-rc5
1.45.0-rc4
1.45.0-rc3
1.45.0-rc20
1.45.0-rc2
1.45.0-rc19
1.45.0-rc18
1.45.0-rc17
1.45.0-rc16
1.45.0-rc15
1.45.0-rc14
1.45.0-rc13
1.45.0-rc12
1.45.0-rc11
1.45.0-rc10
1.45.0-rc1
1.45.0-rc0
1.44.13
1.44.12
1.44.11
1.44.10
1.44.9
1.44.8
1.44.7
1.44.6
1.44.5
1.44.4
1.44.3
1.44.2
1.44.1
1.44.0
1.43.12
1.43.11
1.43.10
1.43.9
1.43.8
1.43.7
1.43.6
1.43.5
1.43.4
1.43.3
1.43.2
1.43.1
1.43.0
1.42.0
1.41.12
1.41.11
1.41.10
1.41.9
1.41.8
1.41.7
1.41.6
1.41.5
1.41.4
1.41.3
1.41.2
1.41.1
1.41.0
1.40.0
1.39.7
1.39.6
1.39.5
1.39.4
1.39.3
1.39.2
1.39.1
1.39.0
1.38.0
1.37.2
1.37.1
1.37.0
1.36.22
1.36.21
1.36.19
1.36.18
1.36.17
1.36.16
1.36.15
1.36.14
1.36.13
1.36.12
1.36.11
1.36.10
1.36.9
1.36.8
1.36.7
1.36.6
1.36.5
1.36.4
1.36.3
1.36.2
1.36.0
1.35.1
1.35.0
1.34.9
1.34.8
1.34.7
1.34.6
1.34.5
1.34.4
1.34.3
1.34.2
1.34.1
1.34.0
1.33.0
1.32.2
1.32.1
1.32.0
1.31.1
1.31.0
1.30.2
1.30.1
1.29.0-rc1
1.29.0-rc0
1.28.1
1.28.0
1.27.1
1.27.0
1.26.13
1.26.12
1.26.11
1.26.10
1.26.9
1.26.8
1.26.7
1.26.6
1.26.5
1.26.4
1.26.2
1.26.1
1.26.0
1.25.8
1.25.7
1.25.6
1.25.5
1.25.4
1.25.3
1.25.2
1.25.1
1.25.0
1.24.2
1.24.1
1.24.0
1.23.3
1.23.2
1.23.1
1.23.0
1.22.1
1.22.0
1.20.1
1.20.0
1.19.1
1.19.0
1.18.1
1.18.0
1.17.1
1.17.0
1.16.2
1.15.1
1.15.0
1.14.0
1.13.4
1.13.3
1.13.2
1.13.1
1.13.0
1.12.0
1.11.1
1.11.0
1.10.0
1.9.0
1.8.0
1.7.0
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.1
1.4.0
1.3.1
1.3.0
1.2.1
1.2.0
1.1.3
1.1.2
1.1.0
1.0.3
1.0.2
1.0.1
1.0.0
0.13.1
0.13.0
0.12.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.0
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.5.0
0.4.0
0.3.0
0.2.0
0.1.9
0.1.8
0.1.3
0.1.1
0.1.0
A declarative, extensible framework for building Elixir applications.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
lib/ash/action_input.ex
defmodule Ash.ActionInput do
@moduledoc """
Input for a custom action
Much like an `Ash.Query` and `Ash.Changeset` are used to provide inputs into
CRUD actions, this struct provides the inputs required to execute a generic
action.
"""
alias Ash.Error.Action.InvalidArgument
require Ash.Flags
defstruct [
:action,
:domain,
:resource,
:tenant,
:to_tenant,
invalid_keys: MapSet.new(),
arguments: %{},
params: %{},
context: %{},
valid?: true,
errors: []
]
@type t :: %__MODULE__{
arguments: map(),
params: map(),
tenant: term(),
action: Ash.Resource.Actions.Action.t() | nil,
resource: Ash.Resource.t(),
invalid_keys: MapSet.t(),
context: map(),
domain: Ash.Domain.t(),
valid?: boolean()
}
@doc """
Create a new action input from a resource.
## Examples
iex> Ash.ActionInput.new(Post)
%Ash.ActionInput{resource: Post}
iex> Ash.ActionInput.new(Post, Domain)
%Ash.ActionInput{resource: Post, domain: Domain}
"""
@spec new(Ash.Resource.t(), Ash.Domain.t()) :: t
def new(resource, domain \\ nil) do
%__MODULE__{resource: resource, domain: domain}
end
@for_action_opts [
domain: [
type: {:spark, Ash.Domain},
doc: "The domain to use for the action. The resource's domain is used by default."
],
context: [
type: :map,
doc: "Context to set on the action input.",
default: %{}
],
authorize?: [
type: {:or, [:boolean, {:literal, nil}]},
doc:
"Whether or not to run authorization on the action. Default behavior of this option is controlled by the domain."
],
tenant: [
type: :any,
doc: "The tenant to use for the action."
],
scope: [
type: :any,
doc:
"A value that implements the `Ash.Scope.ToOpts` protocol, for passing around actor/tenant/context in a single value. See `Ash.Scope.ToOpts` for more."
],
actor: [
type: :any,
doc: "The actor performing the action"
],
skip_unknown_inputs: [
type: {:wrap_list, {:or, [:atom, :string]}},
doc: "A list of unknown inputs to skip. Use `:*` to skip all unknown inputs."
],
tracer: [
type: :any,
doc: "A tracer or list of tracers to trace action execution."
],
private_arguments: [
type: :map,
default: %{},
doc: "A list of private arguments to be set before the action is invoked."
]
]
for_action_opts = @for_action_opts
defmodule Opts do
@moduledoc false
use Spark.Options.Validator, schema: for_action_opts
end
@doc """
Creates a new input for a generic action
## Options
#{Opts.docs()}
## Examples
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> then(& &1.action.name)
:example
"""
@doc spark_opts: [{4, @for_action_opts}]
@spec for_action(
resource_or_input :: Ash.Resource.t() | t(),
action :: atom,
params :: map,
opts :: Keyword.t()
) :: t()
def for_action(resource_or_input, action, params, opts \\ []) do
with {:ok, opts} <- Opts.validate(opts),
{:ok, input} <- set_action_for_input(resource_or_input, action),
opts <- Opts.to_options(opts),
{:ok, input} <- set_domain_for_input(input, opts) do
{input, _opts} = Ash.Actions.Helpers.set_context_and_get_opts(input.domain, input, opts)
input =
Enum.reduce(opts[:private_arguments] || %{}, input, fn {k, v}, input ->
Ash.ActionInput.set_private_argument(input, k, v)
end)
input
|> cast_params(params, opts)
|> set_defaults()
|> require_arguments()
else
{:error, reason} -> raise Ash.Error.to_error_class(reason)
end
end
defp set_action_for_input(%Ash.ActionInput{resource: resource} = input, action_name)
when is_atom(resource) do
case Ash.Resource.Info.action(resource, action_name) do
nil ->
{:error,
Ash.Error.Invalid.NoSuchAction.exception(
resource: resource,
action: action_name,
type: :action
)}
action when action.type == :action ->
{:ok, %{input | action: action}}
action ->
{:error,
Ash.Error.Invalid.NoSuchAction.exception(
resource: resource,
action: action,
type: :action
)}
end
end
defp set_action_for_input(resource, action) when is_atom(resource) do
resource
|> new()
|> set_action_for_input(action)
end
defp set_domain_for_input(input, opts) do
domain =
input.domain || opts[:domain] || Ash.Resource.Info.domain(input.resource) ||
Ash.Actions.Helpers.maybe_embedded_domain(input.resource)
if domain do
{:ok, %{input | domain: domain}}
else
{:error,
ArgumentError.exception(
"Could not determine domain for action. Provide the `domain` option or configure a domain in the resource directly."
)}
end
end
@doc """
Set the tenant to use when calling the action.
## Example
iex> Post
...> |> Ash.ActionInput.new()
...> |> Ash.ActionInput.set_tenant("banana")
...> |> then(& &1.tenant)
"banana"
"""
@spec set_tenant(t(), Ash.ToTenant.t()) :: t()
def set_tenant(input, tenant) do
%{input | tenant: tenant, to_tenant: Ash.ToTenant.to_tenant(tenant, input.resource)}
end
defp require_arguments(input) do
input.action.arguments
|> Enum.filter(&(&1.allow_nil? == false))
|> Enum.reduce(input, fn argument, input ->
case fetch_argument(input, argument.name) do
{:ok, value} when not is_nil(value) ->
input
_ ->
if argument.name in input.invalid_keys do
input
else
add_error(
input,
Ash.Error.Changes.Required.exception(
resource: input.resource,
field: argument.name,
type: :argument
)
)
end
end
end)
end
defp set_defaults(input) do
input.action.arguments
|> Enum.reject(&is_nil(&1.default))
|> Enum.reduce(input, fn argument, input ->
case fetch_argument(input, argument.name) do
{:ok, value} when not is_nil(value) ->
input
_ ->
if argument.name in input.invalid_keys do
input
else
set_argument(input, argument.name, default(argument))
end
end
end)
end
defp default(%{default: {mod, func, args}}), do: apply(mod, func, args)
defp default(%{default: function}) when is_function(function, 0), do: function.()
defp default(%{default: value}), do: value
@doc """
Gets the value of an argument provided to the input.
## Example
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{arg: "banana"})
...> |> Ash.ActionInput.get_argument(:arg)
"banana"
"""
@spec get_argument(t, atom | String.t()) :: term
def get_argument(input, argument) when is_atom(argument) or is_binary(argument) do
case fetch_argument(input, argument) do
{:ok, value} -> value
:error -> nil
end
end
@doc """
Fetches the value of an argument provided to the input or `:error`.
## Examples
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{arg: "banana"})
...> |> Ash.ActionInput.fetch_argument(:arg)
{:ok, "banana"}
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.fetch_argument(:banana)
:error
"""
@spec fetch_argument(t, atom | String.t()) :: {:ok, term()} | :error
def fetch_argument(input, argument) when is_atom(argument) or is_binary(argument) do
with :error <- Map.fetch(input.arguments, argument) do
Map.fetch(input.arguments, to_string(argument))
end
end
@doc """
Set an argument value
## Example
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.set_argument(:arg, "banana")
...> |> Ash.ActionInput.get_argument(:arg)
"banana"
"""
@spec set_argument(input :: t(), name :: atom, value :: term()) :: t()
def set_argument(input, argument, value) do
if input.action do
argument =
Enum.find(
input.action.arguments,
&(&1.name == argument || to_string(&1.name) == argument)
)
if argument do
with value <- Ash.Type.Helpers.handle_indexed_maps(argument.type, value),
constraints <- Ash.Type.include_source(argument.type, input, argument.constraints),
{:ok, casted} <-
Ash.Type.cast_input(argument.type, value, constraints),
{:constrained, {:ok, casted}, argument} when not is_nil(casted) <-
{:constrained, Ash.Type.apply_constraints(argument.type, casted, constraints),
argument} do
%{input | arguments: Map.put(input.arguments, argument.name, casted)}
else
{:constrained, {:ok, nil}, _argument} ->
%{input | arguments: Map.put(input.arguments, argument.name, nil)}
{:constrained, {:error, error}, argument} ->
input = %{
input
| arguments: Map.put(input.arguments, argument.name, value)
}
add_invalid_errors(value, input, argument, error)
{:error, error} ->
input = %{
input
| arguments: Map.put(input.arguments, argument.name, value)
}
add_invalid_errors(value, input, argument, error)
end
else
input
end
else
input
end
end
@doc """
Sets a private argument value
## Example
iex> Post
...> |> Ash.ActionInput.for_action(:example, %{})
...> |> Ash.ActionInput.set_private_argument(:private_arg, "banana")
...> |> Ash.ActionInput.get_argument(:private_arg)
"banana"
"""
@spec set_private_argument(input :: t(), name :: atom, value :: term()) :: t()
def set_private_argument(input, name, value) do
argument =
Enum.find(
input.action.arguments,
&(&1.name == name || to_string(&1.name) == name)
)
cond do
is_nil(argument) ->
input
argument.public? ->
add_invalid_errors(
value,
input,
argument,
"can't set public arguments with set_private_argument/3"
)
true ->
set_argument(input, name, value)
end
end
@doc """
Deep merges the provided map into the input context that can be used later
Do not use the `private` key in your custom context, as that is reserved for
internal use.
## Example
iex> Post
...> |> Ash.ActionInput.new()
...> |> Ash.ActionInput.set_context(%{favourite_fruit: :banana})
...> |> then(& &1.context.favourite_fruit)
:banana
"""
@spec set_context(t(), map | nil) :: t()
def set_context(input, nil), do: input
def set_context(input, map) do
%{
input
| context:
input.context
|> Ash.Helpers.deep_merge_maps(map)
|> then(&Ash.Helpers.deep_merge_maps(&1, map[:shared] || %{}))
}
end
defp cast_params(input, params, opts) do
input = %{
input
| params: Map.merge(input.params, Enum.into(params, %{}))
}
skip_unknown_inputs =
List.wrap(opts[:skip_unknown_inputs] || input.action.skip_unknown_inputs)
Enum.reduce(params, input, fn {name, value}, input ->
cond do
has_argument?(input.action, name) ->
set_argument(input, name, value)
match?("_" <> _, name) ->
input
:* in skip_unknown_inputs ->
input
name in skip_unknown_inputs ->
input
true ->
error =
Ash.Error.Invalid.NoSuchInput.exception(
resource: input.resource,
action: input.action.name,
input: name,
inputs: Enum.map(input.action.arguments, & &1.name)
)
add_error(input, Ash.Error.set_path(error, name))
end
end)
end
defp has_argument?(action, name) when is_atom(name) do
Enum.any?(action.arguments, &(&1.public? && &1.name == name))
end
defp has_argument?(action, name) when is_binary(name) do
Enum.any?(action.arguments, &(&1.public? && to_string(&1.name) == name))
end
defp add_invalid_errors(value, input, attribute, message) do
input = %{input | invalid_keys: MapSet.put(input.invalid_keys, attribute.name)}
messages =
if Keyword.keyword?(message) do
[message]
else
List.wrap(message)
end
Enum.reduce(messages, input, fn message, input ->
if is_exception(message) do
error =
message
|> Ash.Error.to_ash_error()
errors =
case error do
%class{errors: errors}
when class in [
Ash.Error.Invalid,
Ash.Error.Unknown,
Ash.Error.Forbidden,
Ash.Error.Framework
] ->
errors
error ->
[error]
end
Enum.reduce(errors, input, fn error, input ->
add_error(input, Ash.Error.set_path(error, attribute.name))
end)
else
opts = Ash.Type.Helpers.error_to_exception_opts(message, attribute)
Enum.reduce(opts, input, fn opts, input ->
error =
InvalidArgument.exception(
value: value,
field: Keyword.get(opts, :field),
message: Keyword.get(opts, :message),
vars: opts
)
error =
if opts[:path] do
Ash.Error.set_path(error, opts[:path])
else
error
end
add_error(input, error)
end)
end
end)
end
@doc """
Add an error to the errors list and mark the action input as invalid.
See `Ash.Error.to_ash_error/3` for more on supported values for `error`
"""
@spec add_error(
t(),
Ash.Error.error_input() | list(Ash.Error.error_input()),
Ash.Error.path_input()
) :: t()
@spec add_error(t(), Ash.Error.error_input() | list(Ash.Error.error_input())) :: t()
def add_error(input, errors, path \\ [])
def add_error(input, [], _path) do
input
end
def add_error(input, errors, path) when is_list(errors) do
if Keyword.keyword?(errors) do
errors
|> to_change_errors()
|> Ash.Error.set_path(path)
|> handle_error(input)
else
Enum.reduce(errors, input, &add_error(&2, &1, path))
end
end
def add_error(input, error, path) when is_binary(error) do
add_error(
input,
InvalidArgument.exception(message: error),
path
)
end
def add_error(input, error, path) do
error
|> Ash.Error.set_path(path)
|> handle_error(input)
end
defp handle_error(error, input) do
%{input | valid?: false, errors: [error | input.errors]}
end
defp to_change_errors(keyword) do
errors =
if keyword[:fields] && keyword[:fields] != [] do
Enum.map(keyword[:fields], fn field ->
InvalidArgument.exception(
field: field,
message: keyword[:message],
value: keyword[:value],
vars: keyword
)
end)
else
InvalidArgument.exception(
field: keyword[:field],
message: keyword[:message],
value: keyword[:value],
vars: keyword
)
end
if keyword[:path] do
Enum.map(errors, &Ash.Error.set_path(&1, keyword[:path]))
else
errors
end
end
end