defmodule Ash.Resource do
@moduledoc """
A resource is a static definition of an entity in your system.
Resource DSL documentation: `Ash.Resource.Dsl`
"""
@type t :: module
@type record :: struct()
use Ash.Dsl,
single_extension_kinds: [:data_layer],
many_extension_kinds: [
:authorizers,
:notifiers
],
default_extensions: [
data_layer: Ash.DataLayer.Simple,
extensions: [Ash.Resource.Dsl]
]
def init(opts) do
if opts[:data_layer] == :embedded do
{:ok,
opts
|> Keyword.put(:data_layer, Ash.DataLayer.Simple)
|> Keyword.put(:embedded?, true)}
else
{:ok, opts}
end
end
def handle_opts(opts) do
quote bind_quoted: [embedded?: opts[:embedded?]] do
if embedded? do
@persist {:embedded?, true}
require Ash.EmbeddableType
Ash.EmbeddableType.define_embeddable_type()
else
use Ash.Type
@impl true
def storage_type, do: :map
@impl Ash.Type
def cast_input(nil, _), do: nil
def cast_input(%struct{} = value, _) when struct == __MODULE__, do: {:ok, value}
@impl Ash.Type
def cast_stored(_, _),
do:
{:error,
"Cannot cast a non embedded resource from storage. A non-embedded resource may only be used as an argument type."}
@impl Ash.Type
def dump_to_native(_, _),
do:
{:error,
"Cannot dump a non embedded resource to native. A non-embedded resource may only be used as an argument type."}
end
end
end
def handle_before_compile(_opts) do
quote do
require Ash.Schema
if !@moduledoc do
@moduledoc Ash.Resource.Info.description(__MODULE__) || false
end
Ash.Schema.define_schema()
@all_arguments __MODULE__
|> Ash.Resource.Info.actions()
|> Enum.flat_map(& &1.arguments)
|> Enum.map(& &1.name)
|> Enum.uniq()
@arguments_by_action __MODULE__
|> Ash.Resource.Info.actions()
|> Map.new(fn action ->
{action.name, Enum.map(action.arguments, & &1.name)}
end)
@all_attributes __MODULE__
|> Ash.Resource.Info.attributes()
|> Enum.map(& &1.name)
|> Enum.uniq()
if AshPolicyAuthorizer.Authorizer in @extensions do
raise """
AshPolicyAuthorizer has been deprecated and is now built into Ash core.
To use it, replace `authorizers: [AshPolicyAuthorizer.Authorizer]` with `authorizers: [Ash.Policy.Authorizer]`
"""
end
if api = Ash.Resource.Info.define_interface_for(__MODULE__) do
require Ash.CodeInterface
Ash.CodeInterface.define_interface(api, __MODULE__)
end
@doc """
Validates that the keys in the provided input are valid for at least one action on the resource.
Raises a KeyError error at compile time if not. This exists because generally a struct should only ever
be created by Ash as a result of a successful action. You should not be creating records manually in code,
e.g `%MyResource{value: 1, value: 2}`. Generally that is fine, but often with embedded resources it is nice
to be able to validate the keys that are being provided, e.g
```elixir
Resource
|> Ash.Changeset.for_create(:create, %{embedded: EmbeddedResource.input(foo: 1, bar: 2)})
|> MyApp.Api.create()
```
"""
@spec input(values :: map | Keyword.t()) :: map | no_return
def input(opts) do
Map.new(opts, fn {key, value} ->
if key in @all_arguments || key in @all_attributes do
{key, value}
else
raise KeyError, key: key
end
end)
end
@doc """
Same as `input/1`, except restricts the keys to values accepted by the action provided.
"""
@spec input(values :: map | Keyword.t(), action :: atom) :: map | no_return
def input(opts, action) do
case Map.fetch(@arguments_by_action, action) do
:error ->
raise ArgumentError, message: "No such action #{inspect(action)}"
{:ok, args} ->
action = Ash.Resource.Info.action(__MODULE__, action)
Map.new(opts, fn {key, value} ->
if key in action.accept do
{key, value}
else
raise KeyError, key: key
end
end)
end
end
end
end
end