defmodule Ash.Resource.Transformers.ValidateManagedRelationshipOpts do @moduledoc """ Confirms that all action types declared on a resource are supported by its data layer """ use Spark.Dsl.Transformer alias Ash.Changeset.ManagedRelationshipHelpers alias Spark.Dsl.Transformer require Logger def after_compile?, do: true def transform(dsl_state) do relationships = Transformer.get_entities(dsl_state, [:relationships]) dsl_state |> Transformer.get_entities([:actions]) |> Enum.reject(&(&1.type == :read)) |> Enum.each(fn action -> action.changes |> Enum.filter( &match?(%Ash.Resource.Change{change: {Ash.Resource.Change.ManageRelationship, _}}, &1) ) |> Enum.each(fn %Ash.Resource.Change{change: {_, opts}} -> unless Enum.find(action.arguments, &(&1.name == opts[:argument])) do raise Spark.Error.DslError, path: [ :actions, action.type, action.name, :change, :manage_relationship ] ++ Enum.uniq([opts[:argument], opts[:relationship]]), message: "Action #{action.name} has no argument `#{inspect(opts[:argument])}`." end relationship = Enum.find(relationships, &(&1.name == opts[:relationship])) || raise Spark.Error.DslError, path: [ :actions, action.type, action.name, :change, :manage_relationship ] ++ Enum.uniq([opts[:argument], opts[:relationship]]), message: "No such relationship #{opts[:relationship]} exists." if ensure_compiled?(relationship) do try do opts = if opts[:opts][:type] == :replace do Logger.warn( "type: :replace has been renamed to `:append_and_remove` in 2.0, and it will be removed in 2.1" ) Keyword.update!(opts, :opts, fn inner_opts -> Keyword.put(inner_opts, :type, :append_and_remove) end) else opts end manage_opts = if opts[:opts][:type] do defaults = Ash.Changeset.manage_relationship_opts(opts[:opts][:type]) Enum.reduce(defaults, Ash.Changeset.manage_relationship_schema(), fn {key, value}, manage_opts -> Spark.OptionsHelpers.set_default!(manage_opts, key, value) end) else Ash.Changeset.manage_relationship_schema() end opts = Spark.OptionsHelpers.validate!(opts[:opts], manage_opts) ManagedRelationshipHelpers.sanitize_opts(relationship, opts) rescue e -> reraise Spark.Error.DslError, [ path: [ :actions, action.type, action.name, :change, :manage_relationship ] ++ Enum.uniq([opts[:argument], opts[:relationship]]), message: """ The following error was raised when validating options provided to manage_relationship. #{Exception.format(:error, e, __STACKTRACE__)} """ ], __STACKTRACE__ end end end) end) {:ok, dsl_state} end defp ensure_compiled?(%Ash.Resource.Relationships.ManyToMany{ through: through, destination: destination }) do Code.ensure_loaded?(through) && Code.ensure_loaded?(destination) end defp ensure_compiled?(%{destination: destination}) do Code.ensure_loaded?(destination) end end