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 Ash.Dsl.Transformer

  alias Ash.Dsl.Transformer
  alias Ash.Error.Dsl.DslError

  @extension Ash.Resource.Dsl

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

    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(
                module: resource,
                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, resource) do
    actions = Transformer.get_entities(dsl_state, [:actions])

    resource
    |> Ash.Resource.Info.default_actions()
    |> Enum.with_index()
    |> Enum.reduce(dsl_state, fn {type, i}, dsl_state ->
      unless type in [:create, :update, :read, :destroy] do
        raise Ash.Error.Dsl.DslError,
          path: [:actions, :default_actions, i],
          module: resource,
          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