Packages
ash
2.21.7
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/code_interface.ex
defmodule Ash.CodeInterface do
@moduledoc """
Used to define the functions of a code interface for a resource.
"""
@doc false
def require_action(resource, interface) do
action = Ash.Resource.Info.action(resource, interface.action || interface.name)
unless action do
raise Spark.Error.DslError,
module: resource,
message:
"The interface of #{inspect(resource)} refers to a non-existent action #{interface.action || interface.name}",
path: [:interfaces, :interface, interface.name]
end
action
end
@doc false
def default_value(resource, action, key) do
{field_type, field} =
case Enum.find(action.arguments, fn argument ->
argument.name == key
end) do
nil ->
{:attribute, Ash.Resource.Info.attribute(resource, key)}
argument ->
{:argument, argument}
end
cond do
field.allow_nil? && !(field.name in Map.get(action, :require_attributes, [])) ->
:ok
field.name in Map.get(action, :allow_nil_input, []) ->
:ok
!(field.name in Map.get(action, :accept, [])) ->
:ok
true ->
raise "Code interface for #{action.name} has optional argument #{key} but it is not optional"
end
default =
if field_type == :argument do
field.default
else
if action.type == :update || (action.type == :destroy && action.soft?) do
if is_nil(field.update_default) do
field.default
else
field.update_default
end
else
field.default
end
end
if is_function(default) do
quote do
unquote(Macro.escape(default)).()
end
else
quote do
unquote(Macro.escape(default))
end
end
end
def without_optional(keys) do
Enum.map(keys, fn
{:optional, key} ->
key
key ->
key
end)
end
def unwrap_calc_interface_args(keys, resource, arguments, function_head? \\ false) do
{Enum.map(keys, &unwrap_calc_interface_arg_binding(resource, arguments, &1, function_head?)),
Enum.map(keys, &unwrap_calc_interface_arg_access(&1))}
end
defp unwrap_calc_interface_arg_access({:optional, value}),
do: unwrap_calc_interface_arg_access(value)
defp unwrap_calc_interface_arg_access({:optional, value, _}),
do: unwrap_calc_interface_arg_access(value)
defp unwrap_calc_interface_arg_access(value) do
case value do
:_record ->
[type: :_record, name: :record, value: {:record, [], Elixir}]
{tag, name} ->
[type: tag, name: name, value: {name, [], Elixir}]
name ->
[type: :both, name: name, value: {name, [], Elixir}]
end
end
defp unwrap_calc_interface_arg_binding(resource, arguments, {:optional, binding}, head?) do
access = unwrap_calc_interface_arg_binding(resource, arguments, binding, head?)
if head? do
{:\\, [], [access, default_calc_value(resource, arguments, binding)]}
else
access
end
end
defp unwrap_calc_interface_arg_binding(
resource,
arguments,
{:optional, binding, default},
head?
) do
access = unwrap_calc_interface_arg_binding(resource, arguments, binding, head?)
if head? do
{:\\, [], [access, default]}
else
access
end
end
defp unwrap_calc_interface_arg_binding(_resource, _arguments, {tag, value}, _)
when tag in [:arg, :ref] do
{value, [], Elixir}
end
defp unwrap_calc_interface_arg_binding(resource, _arguments, :_record, false) do
{:=, [],
[
{:%, [], [{:__aliases__, [alias: false], [resource]}, {:%{}, [], []}]},
{:record, [], Elixir}
]}
end
defp unwrap_calc_interface_arg_binding(_resource, _arguments, value, _) do
{value, [], Elixir}
end
@doc false
def default_calc_value(_resource, arguments, {:arg, arg_name}) do
arguments
|> Enum.find(&(&1.name == arg_name))
|> case do
nil ->
nil
argument ->
argument.default
end
end
def default_calc_value(resource, _, {:ref, attribute}) do
resource
|> Ash.Resource.Info.attributes()
|> Enum.find(&(&1.name == attribute))
|> case do
nil ->
nil
attribute ->
attribute.default
end
end
def default_calc_value(resource, arguments, name) do
case default_calc_value(resource, arguments, {:arg, name}) do
nil ->
default_calc_value(resource, arguments, {:ref, name})
value ->
value
end
end
@doc false
# sobelow_skip ["DOS.BinToAtom", "DOS.StringToAtom"]
def resolve_calc_method_names(name) do
if name |> to_string() |> String.ends_with?("?") do
safe_name = name |> to_string() |> String.trim_trailing("?") |> String.to_atom()
bang_name = name
{safe_name, bang_name}
else
safe_name = name
bang_name = "#{name}!" |> String.to_atom()
{safe_name, bang_name}
end
end
@doc """
Defines the code interface for a given resource + api combination in the current module. For example:
```elixir
defmodule MyApp.Accounting do
require Ash.CodeInterface
Ash.CodeInterface.define_interface(MyApp.Accounting, MyApp.Accounting.Transaction)
Ash.CodeInterface.define_interface(MyApp.Accounting, MyApp.Accounting.Account)
Ash.CodeInterface.define_interface(MyApp.Accounting, MyApp.Accounting.Invoice)
end
```
Keep in mind that you can have this "automatically" defined in your resources by using the `define_for`
flag in a resource.
For example:
```elixir
defmodule MyApp.Accounting.Transaction do
use Ash.Resource
...
code_interface do
define_for MyApp.Accounting
define :start do
args [:invoice_id]
end
end
end
# Which can now be used like so:
MyApp.Accounting.Transaction.start!(invoice.id)
```
"""
defmacro define_interface(api, resource) do
quote bind_quoted: [api: api, resource: resource], generated: true, location: :keep do
interfaces_for_defaults =
Enum.group_by(Ash.Resource.Info.calculation_interfaces(resource), fn interface ->
{interface.name, Enum.count(interface.args, &is_atom/1), Enum.count(interface.args)}
end)
for {{name, arity, optional_arity}, interfaces} <- interfaces_for_defaults do
args =
case interfaces do
[%{args: args, calculation: calculation}] ->
calculation = Ash.Resource.Info.calculation(resource, calculation)
{arg_bindings, _arg_access} =
args
|> Kernel.||([])
|> Ash.CodeInterface.unwrap_calc_interface_args(
resource,
calculation.arguments,
true
)
arg_bindings
multiple ->
multiple
|> Enum.map(fn interface ->
interface.args
|> Enum.flat_map(fn
{:optional, value} ->
calculation = Ash.Resource.Info.calculation(resource, interface.calculation)
[Ash.CodeInterface.default_calc_value(resource, calculation.arguments, value)]
{:optional, _, value} ->
[value]
_ ->
[]
end)
end)
|> Enum.uniq()
|> case do
[_] ->
interface = hd(multiple)
calculation = Ash.Resource.Info.calculation(resource, interface.calculation)
{arg_bindings, _arg_access} =
interface.args
|> Kernel.||([])
|> Ash.CodeInterface.unwrap_calc_interface_args(
resource,
calculation.arguments,
true
)
arg_bindings
_duplicates ->
raise """
The generated function #{name}/#{arity + optional_arity} would have
multiple different sets of default values for arguments. Please use a different
name for conflicting code interface functions.
"""
end
end
{safe_name, bang_name} = Ash.CodeInterface.resolve_calc_method_names(name)
def unquote(bang_name)(unquote_splicing(args), opts \\ [])
def unquote(safe_name)(unquote_splicing(args), opts \\ [])
end
for interface <- Ash.Resource.Info.calculation_interfaces(resource) do
calculation = Ash.Resource.Info.calculation(resource, interface.calculation)
{arg_bindings, arg_access} =
interface.args
|> Kernel.||([])
|> Ash.CodeInterface.unwrap_calc_interface_args(resource, calculation.arguments)
{safe_name, bang_name} = Ash.CodeInterface.resolve_calc_method_names(interface.name)
@doc "Calculate `#{calculation.name}`, raising any errors. See `#{interface.name}/#{Enum.count(interface.args) + 1}` for more."
def unquote(bang_name)(unquote_splicing(arg_bindings), opts) do
{refs, arguments, record} =
Enum.reduce(
[unquote_splicing(arg_access)],
{%{}, %{}, nil},
fn config, {refs, arguments, record} ->
case config[:type] do
:_record ->
{refs, arguments, config[:value]}
:both ->
{Map.put(refs, config[:name], config[:value]),
Map.put(arguments, config[:name], config[:value]), record}
:ref ->
{Map.put(refs, config[:name], config[:value]), arguments, record}
:arg ->
{refs, Map.put(arguments, config[:name], config[:value]), record}
end
end
)
unquote(api).calculate!(unquote(resource), unquote(interface.calculation),
refs: refs,
args: arguments,
actor: opts[:actor],
record: record
)
end
@doc """
Calculate `#{calculation.name}`, returning `{:ok, result}` or `{:error, error}`.
#{if calculation.description, do: "\nDescription:" <> calculation.description}
"""
def unquote(safe_name)(unquote_splicing(arg_bindings), opts) do
{refs, arguments, record} =
Enum.reduce(
[unquote_splicing(arg_access)],
{%{}, %{}, nil},
fn config, {refs, arguments, record} ->
case config[:type] do
:_record ->
{refs, arguments, config[:value]}
:both ->
{Map.put(refs, config[:name], config[:value]),
Map.put(arguments, config[:name], config[:value]), record}
:ref ->
{Map.put(refs, config[:name], config[:value]), arguments, record}
:arg ->
{refs, Map.put(arguments, config[:name], config[:value]), record}
end
end
)
unquote(api).calculate(unquote(resource), unquote(interface.calculation),
refs: refs,
args: arguments,
actor: opts[:actor],
record: record
)
end
end
for interface <- Ash.Resource.Info.interfaces(resource) do
action = Ash.CodeInterface.require_action(resource, interface)
filter_keys =
if action.type == :read do
if interface.get_by_identity do
Ash.Resource.Info.identity(resource, interface.get_by_identity).keys
else
if interface.get_by do
interface.get_by
end
end
end
args = List.wrap(filter_keys) ++ Ash.CodeInterface.without_optional(interface.args || [])
arg_vars = Enum.map(args, &{&1, [], Elixir})
arg_vars_function =
filter_keys
|> List.wrap()
|> Enum.concat(interface.args || [])
|> Enum.map(fn
{:optional, key} ->
default = Ash.CodeInterface.default_value(resource, action, key)
{:\\, [], [{key, [], Elixir}, default]}
key ->
{key, [], Elixir}
end)
unless Enum.uniq(args) == args do
raise """
Arguments #{inspect(args)} for #{interface.name} are not unique!
"""
end
doc = """
#{action.description || "Calls the #{action.name} action on the #{inspect(resource)} resource."}
## Options
#{Spark.OptionsHelpers.docs(Ash.Resource.Interface.interface_options(action.type))}
"""
resolve_opts_params =
quote do
{params, opts} =
if opts == [] && Keyword.keyword?(params_or_opts),
do: {%{}, params_or_opts},
else: {params_or_opts, opts}
params =
unquote(args)
|> Enum.zip([unquote_splicing(arg_vars)])
|> Enum.reduce(params, fn {key, value}, params ->
Map.put(params, key, value)
end)
end
{subject, subject_args, resolve_subject, act, act!} =
case action.type do
:action ->
subject = quote do: input
resolve_subject =
quote do
{input_opts, opts} =
Keyword.split(opts, [:input, :actor, :tenant, :authorize?, :tracer])
{input, input_opts} = Keyword.pop(input_opts, :input)
input =
input
|> Kernel.||(unquote(resource))
|> Ash.ActionInput.for_action(unquote(action.name), params, input_opts)
end
act = quote do: unquote(api).run_action(input, opts)
act! = quote do: unquote(api).run_action!(input, opts)
{subject, [], resolve_subject, act, act!}
:read ->
subject = quote do: query
resolve_subject =
quote do
{query_opts, opts} =
Keyword.split(opts, [:query, :actor, :tenant, :authorize?, :tracer])
{query, query_opts} = Keyword.pop(query_opts, :query)
query =
if unquote(filter_keys) do
require Ash.Query
{filters, params} = Map.split(params, unquote(filter_keys))
query
|> Kernel.||(unquote(resource))
|> Ash.Query.for_read(unquote(action.name), params, query_opts)
|> Ash.Query.filter(filters)
else
query
|> Kernel.||(unquote(resource))
|> Ash.Query.for_read(unquote(action.name), params, query_opts)
end
end
resolve_not_found_error? =
quote do
{not_found_error?, opts} = Keyword.pop(opts, :not_found_error?)
not_found_error? =
if not_found_error? != nil,
do: not_found_error?,
else: unquote(interface.not_found_error?)
end
act =
if interface.get? || action.get? do
quote do
unquote(resolve_not_found_error?)
unquote(api).read_one(query, opts)
|> case do
{:ok, nil} when not_found_error? ->
{:error, Ash.Error.Query.NotFound.exception(resource: query.resource)}
result ->
result
end
end
else
quote do: unquote(api).read(query, opts)
end
act! =
if interface.get? || action.get? do
quote do
unquote(resolve_not_found_error?)
unquote(api).read_one!(query, opts)
|> case do
nil when not_found_error? ->
raise Ash.Error.Query.NotFound, resource: query.resource
result ->
result
end
end
else
quote do: unquote(api).read!(query, opts)
end
{subject, [], resolve_subject, act, act!}
:create ->
subject = quote do: changeset
resolve_subject =
quote do
{changeset_opts, opts} =
Keyword.split(opts, [:changeset, :actor, :tenant, :authorize?, :tracer])
{changeset, changeset_opts} = Keyword.pop(changeset_opts, :changeset)
changeset =
changeset
|> Kernel.||(unquote(resource))
|> Ash.Changeset.for_create(unquote(action.name), params, changeset_opts)
end
act = quote do: unquote(api).create(changeset, opts)
act! = quote do: unquote(api).create!(changeset, opts)
{subject, [], resolve_subject, act, act!}
:update ->
subject = quote do: changeset
subject_args = quote do: [record]
resolve_subject =
quote do
{changeset_opts, opts} =
Keyword.split(opts, [:actor, :tenant, :authorize?, :tracer])
changeset =
record
|> Ash.Changeset.for_update(unquote(action.name), params, changeset_opts)
end
act = quote do: unquote(api).update(changeset, opts)
act! = quote do: unquote(api).update!(changeset, opts)
{subject, subject_args, resolve_subject, act, act!}
:destroy ->
subject = quote do: changeset
subject_args = quote do: [record]
resolve_subject =
quote do
{changeset_opts, opts} =
Keyword.split(opts, [:actor, :tenant, :authorize?, :tracer])
changeset =
record
|> Ash.Changeset.for_destroy(unquote(action.name), params, changeset_opts)
end
act = quote do: unquote(api).destroy(changeset, opts)
act! = quote do: unquote(api).destroy!(changeset, opts)
{subject, subject_args, resolve_subject, act, act!}
end
subject_name = elem(subject, 0)
resolve_subject =
quote do
unquote(resolve_subject)
unquote(subject) = %{unquote(subject) | api: unquote(api)}
end
common_args =
quote do: [
unquote_splicing(subject_args),
unquote_splicing(arg_vars_function),
params_or_opts \\ %{},
opts \\ []
]
@doc doc
@dialyzer {:nowarn_function, {interface.name, length(common_args)}}
def unquote(interface.name)(unquote_splicing(common_args)) do
unquote(resolve_opts_params)
unquote(resolve_subject)
unquote(act)
end
@doc doc
# sobelow_skip ["DOS.BinToAtom"]
@dialyzer {:nowarn_function, {:"#{interface.name}!", length(common_args)}}
def unquote(:"#{interface.name}!")(unquote_splicing(common_args)) do
unquote(resolve_opts_params)
unquote(resolve_subject)
unquote(act!)
end
# sobelow_skip ["DOS.BinToAtom"]
@dialyzer {:nowarn_function,
{:"#{subject_name}_to_#{interface.name}", length(common_args)}}
def unquote(:"#{subject_name}_to_#{interface.name}")(unquote_splicing(common_args)) do
unquote(resolve_opts_params)
unquote(resolve_subject)
unquote(subject)
end
# sobelow_skip ["DOS.BinToAtom"]
@dialyzer {:nowarn_function, {:"can_#{interface.name}", length(common_args) + 1}}
def unquote(:"can_#{interface.name}")(actor, unquote_splicing(common_args)) do
unquote(resolve_opts_params)
opts = Keyword.put(opts, :actor, actor)
unquote(resolve_subject)
unquote(api).can(unquote(subject), actor, opts)
end
# sobelow_skip ["DOS.BinToAtom"]
@dialyzer {:nowarn_function, {:"can_#{interface.name}?", length(common_args) + 1}}
def unquote(:"can_#{interface.name}?")(actor, unquote_splicing(common_args)) do
unquote(resolve_opts_params)
opts = Keyword.put(opts, :actor, actor)
unquote(resolve_subject)
unquote(api).can?(unquote(subject), actor, opts)
end
end
end
end
end