# SPDX-FileCopyrightText: 2022 Alembic Pty Ltd # # SPDX-License-Identifier: MIT defmodule AshAuthentication.Validations.Attribute do @moduledoc """ Validation helpers for Resource attributes. """ alias Ash.Resource.Info alias Spark.Error.DslError import AshAuthentication.Utils @doc """ Validate that an option is set correctly on an attribute """ @spec validate_attribute_option(Ash.Resource.Attribute.t(), module, atom, [any]) :: :ok | {:error, Exception.t()} def validate_attribute_option(attribute, resource, field, values) do with {:ok, value} <- Map.fetch(attribute, field), true <- value in values do :ok else :error -> {:error, DslError.exception( module: resource, path: [:actions, :attribute], message: "The attribute `#{inspect(attribute.name)}` on the `#{inspect(resource)}` resource is missing the `#{inspect(field)}` property" )} false -> case values do [] -> {:error, DslError.exception( module: resource, path: [:actions, :attribute], message: "The attribute `#{inspect(attribute.name)}` on the `#{inspect(resource)}` resource is should not have `#{inspect(field)}` set" )} [expected] -> {:error, DslError.exception( module: resource, path: [:actions, :attribute], message: "The attribute `#{inspect(attribute.name)}` on the `#{inspect(resource)}` resource should have `#{inspect(field)}` set to `#{inspect(expected)}`" )} expected -> expected = expected |> Enum.map(&"`#{inspect(&1)}`") |> to_sentence(final: "or") {:error, DslError.exception( module: resource, path: [:actions, :attribute], message: "The attribute `#{inspect(attribute.name)}` on the `#{inspect(resource)}` resource should have `#{inspect(field)}` set to one of #{expected}" )} end end end @doc """ Validate than an attribute has a unique identity applied. """ @spec validate_attribute_unique_constraint(map, [atom], module) :: :ok | {:error, Exception.t()} def validate_attribute_unique_constraint(dsl_state, fields, resource) do fields = MapSet.new(fields) identities = dsl_state |> Info.identities() |> Enum.map(&MapSet.new(&1.keys)) primary_key = dsl_state |> Info.primary_key() |> MapSet.new() [primary_key | identities] |> Enum.find(&MapSet.equal?(&1, fields)) |> case do nil -> message = case Enum.to_list(fields) do [field] -> "The `#{inspect(field)}` attribute on the resource `#{inspect(resource)}` should be uniquely constrained" [_ | _] = fields -> fields = fields |> Enum.map(&"`#{&1}`") |> to_sentence(final: "and") "The #{fields} attributes on the resource `#{inspect(resource)}` should be uniquely constrained" end {:error, DslError.exception(path: [:identities, :identity], message: message)} _ -> :ok end end end