lib/reactor/dsl/step.ex

defmodule Reactor.Dsl.Step do
  @moduledoc """
  The struct used to store step DSL entities.

  See `d:Reactor.step`.
  """

  defstruct __identifier__: nil,
            arguments: [],
            async?: true,
            compensate: nil,
            impl: nil,
            max_retries: :infinity,
            name: nil,
            run: nil,
            transform: nil,
            undo: nil

  alias Reactor.{Builder, Dsl}

  @type t :: %__MODULE__{
          arguments: [Dsl.Argument.t()],
          async?: boolean,
          compensate:
            nil | (any, Reactor.inputs(), Reactor.context() -> :ok | :retry | {:continue, any}),
          impl: module | {module, keyword},
          max_retries: non_neg_integer() | :infinity,
          name: atom,
          run:
            nil
            | (Reactor.inputs(), Reactor.context() ->
                 {:ok, any} | {:ok, any, [Reactor.Step.t()]} | {:halt | :error, any}),
          transform: nil | (any -> any),
          undo: nil | (any, Reactor.inputs(), Reactor.context() -> :ok | :retry | {:error, any}),
          __identifier__: any
        }

  defimpl Dsl.Build do
    alias Spark.Error.DslError

    def build(step, reactor) do
      with {:ok, step} <- rewrite_step(step, reactor.id) do
        Builder.add_step(reactor, step.name, step.impl, step.arguments,
          async?: step.async?,
          max_retries: step.max_retries,
          transform: step.transform,
          ref: :step_name
        )
      end
    end

    def transform(_step, dsl_state), do: {:ok, dsl_state}

    def verify(_step, _dsl_state), do: :ok

    defp rewrite_step(step, module) when is_nil(step.impl) and is_nil(step.run),
      do:
        {:error,
         DslError.exception(
           module: module,
           path: [:reactor, :step, step.name],
           message: "Step has no implementation"
         )}

    defp rewrite_step(step, module) when not is_nil(step.impl) and not is_nil(step.run),
      do:
        {:error,
         DslError.exception(
           module: module,
           path: [:reactor, :step, step.name],
           message: "Step has both an implementation module and a run function"
         )}

    defp rewrite_step(step, module)
         when not is_nil(step.impl) and not is_nil(step.compensate),
         do:
           {:error,
            DslError.exception(
              module: module,
              path: [:reactor, :step, step.name],
              message: "Step has both an implementation module and a compensate function"
            )}

    defp rewrite_step(step, module) when not is_nil(step.impl) and not is_nil(step.undo),
      do:
        {:error,
         DslError.exception(
           module: module,
           path: [:reactor, :step, step.name],
           message: "Step has both an implementation module and a undo function"
         )}

    defp rewrite_step(step, _dsl_state)
         when is_nil(step.run) and is_nil(step.compensate) and is_nil(step.undo) and
                not is_nil(step.impl),
         do: {:ok, step}

    defp rewrite_step(step, _dsl_state),
      do:
        {:ok,
         %{
           step
           | impl:
               {Reactor.Step.AnonFn, run: step.run, compensate: step.compensate, undo: step.undo},
             run: nil,
             compensate: nil,
             undo: nil
         }}
  end
end