lib/ash/resource/transformers/validate_eager_identities.ex

defmodule Ash.Resource.Transformers.ValidateEagerIdentities do
  @moduledoc """
  Confirms that eager identities are not declared on a resource with no primary read.
  """
  use Spark.Dsl.Transformer

  alias Spark.Dsl.Transformer
  alias Spark.Error.DslError

  def after_compile?, do: true

  def transform(dsl_state) do
    primary_read =
      dsl_state
      |> Transformer.get_entities([:actions])
      |> Enum.find(&(&1.type == :read && &1.primary?))

    dsl_state
    |> Transformer.get_entities([:identities])
    |> Enum.filter(&(&1.eager_check_with || &1.pre_check_with))
    |> case do
      [] ->
        {:ok, dsl_state}

      eager ->
        attributes = Transformer.get_entities(dsl_state, [:attributes])

        if primary_read do
          non_attributes =
            Enum.filter(eager, fn identity ->
              Enum.any?(identity.keys, fn key ->
                !Enum.any?(attributes, &(&1.name == key))
              end)
            end)

          case non_attributes do
            [] ->
              {:ok, dsl_state}

            [identity] ->
              {:error,
               DslError.exception(
                 path: [:identities, identity.name],
                 message:
                   "Identity #{identity.name} is declared with `eager_check_with` or `pre_check_with` but not all of the `keys` are attributes."
               )}

            identities ->
              {:error,
               DslError.exception(
                 path: [:identities],
                 message:
                   "Identities #{Enum.map_join(identities, ",", & &1.name)} are declared with `eager_check_with` or `pre_check_with` but not all of the `keys` are attributes."
               )}
          end
        else
          names = Enum.map(eager, & &1.name)

          case names do
            [name] ->
              {:error,
               DslError.exception(
                 path: [:identities, name],
                 message:
                   "Identity #{name} is declared with `eager_check_with` or `pre_check_with` but the resource has no primary read action."
               )}

            names ->
              {:error,
               DslError.exception(
                 path: [:identities],
                 message:
                   "Identities #{Enum.join(names, ",")} are declared with `eager_check_with` or `pre_check_with` but the resource has no primary read action."
               )}
          end
        end
    end
  end
end