Current section
Files
Jump to
Current section
Files
lib/ash/resource/validation.ex
defmodule Ash.Resource.Validation do
@moduledoc """
Represents a validation in Ash.
See `Ash.Resource.Validation.Builtin` for a list of builtin validations.
To write your own validation, define a module that implements the `c:init/1` callback
to validate options at compile time, and `c:validate/2` callback to do the validation.
Then, in a resource, you can say:
```
validations do
validation {MyValidation, [foo: :bar]}
end
```
To make it more readable, you can define a function in the module that returns that tuple,
and import it into your resource.
```
defmodule MyValidation do
def my_validation(value) do
{__MODULE__, foo: value}
end
end
```
```
defmodule MyResource do
...
import MyValidation
validations do
validate my_validation(:foo)
end
end
```
"""
defstruct [:validation, :module, :opts, :expensive?, on: []]
@type t :: %__MODULE__{}
@type path :: [atom | integer]
@callback init(Keyword.t()) :: {:ok, Keyword.t()} | {:error, String.t()}
@callback validate(Ash.changeset(), Keyword.t()) :: :ok | {:error, Ash.error()}
@schema [
validation: [
type: {:custom, __MODULE__, :validation, []},
required: true,
doc: "The module/opts pair of the validation"
],
on: [
type: {:custom, __MODULE__, :on, []},
default: [:create, :update],
doc: """
The action types the validation should run on.
Many validations don't make sense in the context of deletion, so by default it is left out of the list.
"""
],
expensive?: [
type: :boolean,
default: false,
doc:
"If a validation is expensive, it won't be run on invalid changes. All inexpensive validations are always run, to provide informative validations."
]
]
def transform(%__MODULE__{validation: {module, opts}} = validation) do
{:ok, %{validation | module: module, opts: opts}}
end
def opt_schema, do: @schema
def on(list) do
list
|> List.wrap()
|> Enum.all?(&(&1 in [:create, :update, :destroy]))
|> case do
true ->
{:ok, List.wrap(list)}
false ->
{:error, "Expected items of [:create, :update, :destroy], got: #{inspect(list)}"}
end
end
def validation({module, opts}) when is_atom(module) do
if Keyword.keyword?(opts) do
case module.init(opts) do
{:ok, opts} ->
{:ok, {module, opts}}
{:error, error} ->
{:error, error}
end
else
{:error, "Validation must be a `{module, opts}` tuple"}
end
end
def validation(module) when is_atom(module) do
validation({module, []})
end
def validation(_) do
{:error, "Validation must be a `{module, opts}` tuple"}
end
end