Packages

A resource declaration and interaction library. Built with pluggable data layers, and designed to be used by multiple front ends.

Retired package: Renamed - renamed to ash

Current section

Files

Jump to
ash_core lib ash resource validation present.ex
Raw

lib/ash/resource/validation/present.ex

defmodule Ash.Resource.Validation.Present do
@moduledoc "Validates that the provided attribute is non-nil"
alias Ash.Error.Changes.{InvalidAttribute, InvalidChanges}
@behaviour Ash.Resource.Validation
@opt_schema [
at_least: [
type: :non_neg_integer,
doc: "At least this many must be present. Defaults to the number of attributes provided"
],
at_most: [
type: :non_neg_integer,
doc: "At most this many must be present. Defaults to the number of attributes provided"
],
exactly: [
type: :non_neg_integer,
doc: "Exactly this many must be present"
],
attributes: [
type: {:custom, __MODULE__, :attributes, []},
required: true,
doc: "The attributes that the configured amount of must be present"
]
]
@impl true
def init(opts) do
case NimbleOptions.validate(opts, @opt_schema) do
{:ok, opts} ->
{:ok, opts}
{:error, error} ->
{:error, Exception.message(error)}
end
end
@impl true
def validate(changeset, opts) do
{present, count} =
Enum.reduce(opts[:attributes], {0, 0}, fn attribute, {present, count} ->
if is_nil(Ash.Changeset.get_attribute(changeset, attribute)) do
{present, count + 1}
else
{present + 1, count + 1}
end
end)
cond do
opts[:exactly] && present != opts[:exactly] ->
if opts[:exactly] == 0 do
changes_error(opts, count, "must be absent")
else
if count == 1 do
attribute_error(opts, count, "must be present")
else
attribute_error(opts, count, "exactly #{opts[:exactly]} must be present")
end
end
opts[:at_least] && present < opts[:at_least] ->
if count == 1 do
attribute_error(opts, count, "must be present")
else
changes_error(opts, count, "at least #{opts[:at_least]} must be present")
end
opts[:at_most] && present > opts[:at_most] ->
if count == 1 do
attribute_error(opts, count, "must not be present")
else
changes_error(opts, count, "at least #{opts[:at_least]} must be present")
end
true ->
:ok
end
end
defp changes_error(opts, count, message) do
{:error,
InvalidChanges.exception(
fields: opts[:attributes],
validation: {:present, opts[:at_least] || count, opts[:at_most] || count},
message: message
)}
end
defp attribute_error(opts, count, message) do
{:error,
InvalidAttribute.exception(
field: List.first(opts[:attributes]),
validation: {:present, opts[:at_least] || count, opts[:at_most] || count},
message: message
)}
end
@doc false
def attributes(attributes) do
attributes = List.wrap(attributes)
if Enum.all?(attributes, &is_atom/1) do
{:ok, attributes}
else
{:error, "Expected all attributes provided to be atoms."}
end
end
end