# SPDX-FileCopyrightText: 2019 ash contributors # # SPDX-License-Identifier: MIT defimpl Reactor.Dsl.Build, for: Ash.Reactor.Dsl.BulkUpdate do @moduledoc false alias Ash.Reactor.BulkUpdateStep alias Ash.Resource.Info alias Reactor.{Argument, Builder} alias Spark.{Dsl.Transformer, Error.DslError} import Ash.Reactor.BuilderUtils import Reactor.Template, only: :macros @doc false @impl true def build(bulk_update, reactor) do with {:ok, reactor, arguments} <- build_input_arguments(reactor, bulk_update) do initial = %Argument{name: :initial, source: bulk_update.initial} notification_metadata = case bulk_update.notification_metadata do template when is_template(template) -> %Argument{name: :notification_metadata, source: template} map when is_map(map) -> Argument.from_value(:notification_metadata, map) end arguments = arguments |> maybe_append(bulk_update.actor) |> maybe_append(bulk_update.tenant) |> maybe_append(bulk_update.load) |> maybe_append(bulk_update.context) |> Enum.concat(bulk_update.wait_for) |> Enum.concat([initial, notification_metadata]) action_options = bulk_update |> Map.take([ :action, :allow_stream_with, :assume_casted?, :atomic_update, :authorize_changeset_with, :authorize_query_with, :authorize_query?, :authorize?, :batch_size, :domain, :filter, :lock, :max_concurrency, :notify?, :page, :read_action, :resource, :return_errors?, :return_records?, :return_stream?, :reuse_values?, :rollback_on_error?, :select, :skip_unknown_inputs, :sorted?, :stop_on_error?, :strategy, :stream_batch_size, :stream_with, :timeout, :transaction, :undo_action, :undo ]) |> Map.put(:return_notifications?, bulk_update.notify?) |> Enum.reject(&is_nil(elem(&1, 1))) step_options = bulk_update |> Map.take([:async?]) |> Map.put(:ref, :step_name) |> Enum.to_list() Builder.add_step( reactor, bulk_update.name, {BulkUpdateStep, action_options}, arguments, step_options ) end end @doc false @impl true def verify(bulk_update, dsl_state) do action_error_path = [:bulk_update, bulk_update.name, :action] with {:ok, action} <- get_action(dsl_state, bulk_update.resource, bulk_update.action, action_error_path), :ok <- verify_action_type(dsl_state, bulk_update.resource, action, :update, action_error_path), :ok <- verify_undo(dsl_state, bulk_update), :ok <- maybe_verify_undo_action(dsl_state, bulk_update), :ok <- verify_select(dsl_state, bulk_update), :ok <- verify_rollback_on_error(dsl_state, bulk_update), :ok <- verify_sorted(dsl_state, bulk_update) do verify_notify(dsl_state, bulk_update) end end defguardp is_falsy(value) when value in [nil, false] defp verify_notify(dsl_state, bulk_update) when bulk_update.notify? == true and bulk_update.return_stream? == true, do: {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: [:bulk_update, bulk_update.name, :notify?], message: """ Setting `notify?` has no effect when `return_stream?` is `true`. You must manually consume the resulting stream of records and notifications in a subsequent step. """ )} defp verify_notify(_dsl_state, _bulk_update), do: :ok defp verify_sorted(dsl_state, bulk_update) when bulk_update.sorted? == true and is_falsy(bulk_update.return_records?), do: {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: [:bulk_update, bulk_update.name, :sorted?], message: "Setting `sorted?` has no effect with `return_records?` is not `true`." )} defp verify_sorted(_dsl_state, _bulk_update), do: :ok defp verify_rollback_on_error(dsl_state, bulk_update) when bulk_update.rollback_on_error? == true and is_falsy(bulk_update.transaction), do: {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: [:bulk_update, bulk_update.name, :rollback_on_error?], message: "Setting `rollback_on_error?` has no effect when `transaction` is `false`." )} defp verify_rollback_on_error(_dsl_state, _bulk_update), do: :ok defp verify_select(_dsl_state, bulk_update) when bulk_update.select == [], do: :ok defp verify_select(_dsl_state, bulk_update) when bulk_update.return_records? == true, do: :ok defp verify_select(dsl_state, bulk_update), do: {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: [:bulk_update, bulk_update.name, :select], message: "Setting `select` has no effect when `return_records?` is not `true`." )} defp verify_undo(dsl_state, bulk_update) when bulk_update.undo != :never and bulk_update.return_stream? == true, do: {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: [:bulk_update, bulk_update.name, :undo], message: "Cannot set undo to anything other than `:never` when `return_stream?` is `true`." )} defp verify_undo(_dsl_state, _bulk_update), do: :ok defp maybe_verify_undo_action(_dsl_state, bulk_update) when bulk_update.undo == :never, do: :ok defp maybe_verify_undo_action(dsl_state, bulk_update) do error_path = [:bulk_update, bulk_update.name, :undo_action] with {:ok, action} <- get_action(dsl_state, bulk_update.resource, bulk_update.undo_action, error_path), :ok <- verify_action_type(dsl_state, bulk_update.resource, action, :action, error_path) do verify_action_takes_bulk_result(dsl_state, bulk_update.resource, action, error_path) end end defp get_action(dsl_state, resource, action_name, error_path) do case Info.action(resource, action_name) do nil -> {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: error_path, message: "No action found matching the name `#{action_name}` on resource `#{inspect(resource)}`." )} action when is_struct(action) -> {:ok, action} end end defp verify_action_type(_dsl_state, _resource, action, action_type, _error_path) when action.type == action_type, do: :ok defp verify_action_type(dsl_state, resource, action, action_type, error_path) do {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: error_path, message: "Expected the action `#{inspect(action.name)}` on `#{inspect(resource)}` to be a #{action_type}, however it is a #{action.type}" )} end defp verify_action_takes_bulk_result( _dsl_state, _resource, %{arguments: [%{name: :bulk_result}]}, _error_path ), do: :ok defp verify_action_takes_bulk_result(dsl_state, _resource, _action, error_path) do {:error, DslError.exception( module: Transformer.get_persisted(dsl_state, :module), path: error_path, message: "The undo action for a bulk update step should take a single `bulk_result` argument." )} end end