From d7aaec7499b4aadf4724f0251c3ad4a7255db279 Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Fri, 28 Apr 2023 16:05:58 -0400 Subject: [PATCH] feat: `attribute_in/2` builtin validation --- lib/ash/resource/validation/attribute_in.ex | 47 +++++++++++++++++++++ lib/ash/resource/validation/builtins.ex | 15 +++++++ 2 files changed, 62 insertions(+) create mode 100644 lib/ash/resource/validation/attribute_in.ex diff --git a/lib/ash/resource/validation/attribute_in.ex b/lib/ash/resource/validation/attribute_in.ex new file mode 100644 index 00000000..3eb6fdd7 --- /dev/null +++ b/lib/ash/resource/validation/attribute_in.ex @@ -0,0 +1,47 @@ +defmodule Ash.Resource.Validation.AttributeIn do + @moduledoc false + + @opt_schema [ + attribute: [ + type: :atom, + required: true, + doc: "The attribute to check" + ], + list: [ + type: :any, + required: true, + doc: "The list of values that the attribute must be in" + ] + ] + + use Ash.Resource.Validation + alias Ash.Error.Changes.InvalidAttribute + + @impl true + def init(opts) do + case Spark.OptionsHelpers.validate(opts, @opt_schema) do + {:ok, opts} -> + {:ok, opts} + + {:error, error} -> + {:error, Exception.message(error)} + end + end + + @impl true + def validate(changeset, opts) do + value = Ash.Changeset.get_attribute(changeset, opts[:attribute]) + + if value in opts[:list] do + :ok + else + {:error, + InvalidAttribute.exception( + value: value, + field: opts[:attribute], + message: "must equal %{value}", + vars: [field: opts[:attribute], value: opts[:value]] + )} + end + end +end diff --git a/lib/ash/resource/validation/builtins.ex b/lib/ash/resource/validation/builtins.ex index 8659b07a..378224aa 100644 --- a/lib/ash/resource/validation/builtins.ex +++ b/lib/ash/resource/validation/builtins.ex @@ -88,6 +88,21 @@ defmodule Ash.Resource.Validation.Builtins do {Validation.AttributeEquals, attribute: attribute, value: value} end + @doc """ + Validates that an attribute is being changed to one of a set of specific values, or is in the the given list if it is not being changed. + + ## Examples + + validate attribute_in(:state, [1, 2, 3]) + + # Or to only check for changing to a something in a given list + validate attribute_in(:state, [1, 2, 3]), where: [changing(:state)] + """ + @spec attribute_in(attribute :: atom, list :: [term]) :: Validation.ref() + def attribute_in(attribute, list) do + {Validation.AttributeIn, attribute: attribute, list: list} + end + @string_length_opts [ min: [ type: :non_neg_integer,