defmodule ArcadiaCloud.Provisioning.SagaState do @moduledoc """ Carries the live saga context across step calls. Steps receive this struct, mutate it via the helpers, return a new value. `context` is the accumulating bag of created-resource IDs and other per-saga state. Outputs from a step (e.g. `droplet_id` after CreateDroplet) live here so later steps and compensation can find what to act on. `inputs` are the immutable arguments the saga was started with (e.g. `template_id`, `deployment_slug`). Steps may read these but never write to them. """ alias ArcadiaCloud.Provisioning.SagaRun defstruct [:saga_id, :saga, :context, :inputs, :step_idx] @type t :: %__MODULE__{ saga_id: binary(), saga: SagaRun.t() | nil, context: map(), inputs: map(), step_idx: non_neg_integer() | nil } def from(%SagaRun{} = saga) do %__MODULE__{ saga_id: saga.id, saga: saga, context: saga.context || %{}, inputs: (saga.context || %{})["__inputs__"] || %{}, step_idx: saga.current_step_idx } end @doc """ Idempotency hook: was a value written by a prior attempt of this same step? Returns the value or nil. """ def get_output(%__MODULE__{context: ctx}, key) when is_atom(key) do get_output(%__MODULE__{context: ctx}, Atom.to_string(key)) end def get_output(%__MODULE__{context: ctx}, key) when is_binary(key) do Map.get(ctx, key) end def put_output(%__MODULE__{} = state, key, value) when is_atom(key) do put_output(state, Atom.to_string(key), value) end def put_output(%__MODULE__{context: ctx} = state, key, value) when is_binary(key) do %{state | context: Map.put(ctx, key, value)} end def get_input(%__MODULE__{inputs: inputs}, key) when is_atom(key) do Map.get(inputs, Atom.to_string(key)) end def get_input(%__MODULE__{inputs: inputs}, key) when is_binary(key) do Map.get(inputs, key) end end