StatifierOban.Timer.JobArgs (StatifierOban v0.3.0)

Copy Markdown View Source

The wire shape between a %Statifier.Effect.SendDelayed{} and the args map an Oban job stores.

Oban args live as JSON, so this module owns the (de)serialization of the one effect a timer job carries, plus the scope it was scheduled under. Two rules shape it:

  • Every deterministic field rides as an explicit JSON value: the dedup pair (scope, ordinal) at the top level - Oban's uniqueness keys read args at the top level - and the row data ADR-0059 (statifier-ex) keeps beside the key (send_id, macrostep, microstep, round, c_index, owner), self-describing in the store during an incident.
  • The two host-opaque fields, data and caller_context, are arbitrary terms with no JSON shape, so they ride as tagged :erlang.term_to_binary/1 payloads (StatifierOban.OpaqueTerm) and come back byte-identical. caller_context stays row data, never a key component (st-ADR-0063 decision 6). Decoding uses :safe, so a payload naming an atom the reading node has never seen decodes to a typed error rather than minting atoms. from_effect/3's optional codec runs over both fields' bytes and tags the payload with its module name (StatifierOban.OpaqueTerm.Codec); to_effect/1 reads whatever tag the stored row carries, regardless of what the reading caller passed.

to_effect/1 is the exact inverse of from_effect/3 for every %SendDelayed{} the scheduler accepts: what the fired job carries is enough to rebuild the event and its position, per ADR-0054's correlation rule - position is read off the stored effect, never recomputed at delivery.

Summary

Types

String-keyed args map as Oban stores and redelivers it.

Why from_effect/3 could not build an args map.

Functions

Builds the args map for a timer job from the scope and the effect.

Rebuilds the scope and the %SendDelayed{} from a job's args.

Types

args()

@type args() :: %{optional(String.t()) => term()}

String-keyed args map as Oban stores and redelivers it.

decode_error()

@type decode_error() ::
  {:missing_field, String.t()}
  | {:invalid_field, String.t(), term()}
  | StatifierOban.OpaqueTerm.decode_error()

encode_error()

@type encode_error() ::
  {:codec_failed, String.t(), StatifierOban.OpaqueTerm.encode_error()}

Why from_effect/3 could not build an args map.

Functions

from_effect(scope, effect, codec \\ nil)

@spec from_effect(String.t(), Statifier.Effect.SendDelayed.t(), module() | nil) ::
  {:ok, args()} | {:error, encode_error()}

Builds the args map for a timer job from the scope and the effect.

The caller has already validated the scope and ordinal via StatifierOban.Timer.Key.dedup_key/2; this function only lays fields out on the wire. data and caller_context are encoded through a with, so the first codec failure short-circuits and no partially-encoded args map is ever returned.

to_effect(args)

@spec to_effect(args()) ::
  {:ok, String.t(), Statifier.Effect.SendDelayed.t()} | {:error, decode_error()}

Rebuilds the scope and the %SendDelayed{} from a job's args.

Returns a typed error rather than raising: an undecodable job is a fact about the row, and the worker decides what to do with it.