lib/ash/resource/transformers/set_primary_actions.ex

defmodule Ash.Resource.Transformers.ValidatePrimaryActions do
  @moduledoc """
  Validates the primary action configuration

  If multiple primary actions exist this results in an error.
  """
  use Spark.Dsl.Transformer

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

  @extension Ash.Resource.Dsl

  def transform(dsl_state) do
    dsl_state = add_defaults(dsl_state)

    dsl_state
    |> Transformer.get_entities([:actions])
    |> Enum.group_by(& &1.type)
    |> Map.put_new(:read, [])
    |> Map.put_new(:update, [])
    |> Map.put_new(:create, [])
    |> Map.put_new(:destroy, [])
    |> Enum.reduce_while({:ok, dsl_state}, fn
      {_type, []}, {:ok, dsl_state} ->
        {:cont, {:ok, dsl_state}}

      {type, actions}, {:ok, dsl_state} ->
        case Enum.count_until(actions, & &1.primary?, 2) do
          2 ->
            {:halt,
             {:error,
              DslError.exception(
                message:
                  "Multiple actions of type #{type} configured as `primary?: true`, but only one action per type can be the primary",
                path: [:actions, type]
              )}}

          _ ->
            {:cont, {:ok, dsl_state}}
        end
    end)
  end

  defp add_defaults(dsl_state) do
    actions = Transformer.get_entities(dsl_state, [:actions])

    default_defaults =
      if Transformer.get_persisted(dsl_state, :embedded?) do
        [:create, :read, :update, :destroy]
        |> Enum.reject(fn action_name ->
          Enum.any?(actions, &(&1.name == action_name))
        end)
      else
        []
      end

    dsl_state
    |> Transformer.get_option([:actions], :defaults)
    |> Kernel.||(default_defaults)
    |> Enum.with_index()
    |> Enum.reduce(dsl_state, fn {type, i}, dsl_state ->
      unless type in [:create, :update, :read, :destroy] do
        raise Spark.Error.DslError,
          path: [:actions, :default_actions, i],
          message: "#{type} is not a valid action type"
      end

      primary? = !Enum.any?(actions, &(&1.type == type && &1.primary?))

      {:ok, action} =
        Transformer.build_entity(@extension, [:actions], type,
          name: type,
          primary?: primary?
        )

      Transformer.add_entity(dsl_state, [:actions], action)
    end)
  end
end