StatifierOban.OpaqueTerm (StatifierOban v0.7.0)

Copy Markdown View Source

Tagged :erlang.term_to_binary/1 payloads for the host-opaque fields a job's args carry.

Oban args live as JSON, and some Statifier effect fields (data, caller_context, an invoke's params and content) are arbitrary terms with no JSON shape - so they ride as Base64-encoded external term format under a single tag key, and come back byte-identical. nil stays nil, so the common case costs nothing and stays readable in the row.

encode/2 optionally takes a StatifierOban.OpaqueTerm.Codec implementation and runs it over the bytes before they are Base64-encoded; when it does, the payload also carries the codec's module name under a "codec" tag, and decode_field/2 resolves that name and runs the codec's decode/1 before binary_to_term. A payload with no "codec" tag - every row written before a host adopted a codec, and every row written with none configured - takes the plain path unchanged, so a codec is opt-in per payload, not per deploy.

Decoding uses :safe, so a payload naming an atom the reading node has never seen decodes to a typed error rather than minting atoms. Both job wire modules (StatifierOban.Timer.JobArgs, StatifierOban.Invoke.JobArgs) share this one encoding, which is what keeps their rows mutually readable during an incident.

Summary

Types

Why a stored payload could not be decoded.

Why encode/2 could not produce a payload.

Functions

Decodes the tagged payload stored under field in args.

Encodes term as a tagged payload map, or nil for nil.

Types

decode_error()

@type decode_error() ::
  {:invalid_field, String.t(), term()}
  | {:invalid_codec, String.t(), term()}
  | {:codec_failed, String.t(), module(), term()}

Why a stored payload could not be decoded.

encode_error()

@type encode_error() :: {:codec_failed, module(), term()}

Why encode/2 could not produce a payload.

Functions

decode_field(args, field)

@spec decode_field(map(), String.t()) :: {:ok, term()} | {:error, decode_error()}

Decodes the tagged payload stored under field in args.

A missing or nil field decodes to {:ok, nil} - the exact inverse of encode/2's nil arm. A payload carrying a "codec" tag resolves that module name and runs its decode/1 on the Base64-decoded bytes before binary_to_term, ignoring whatever codec (if any) this call's own caller configured - the tag on the row, not the reader's configuration, decides. Anything that is neither nil nor a well-formed tagged payload is a typed error about the row, returned as data rather than raised: the caller (a worker at its boundary) decides what a corrupt row costs.

encode(term, codec \\ nil)

@spec encode(term(), module() | nil) ::
  {:ok, nil | %{required(String.t()) => String.t()}} | {:error, encode_error()}

Encodes term as a tagged payload map, or nil for nil.

With no codec (the default), the payload is exactly %{"t2b64" => base64} - byte-identical to every row written before this seam existed. With a codec, the payload also carries "codec" naming the module, and codec.encode/1 runs over the term's bytes first; a codec that fails - returns {:error, _}, returns a non-binary, or raises - produces {:error, {:codec_failed, codec, reason}} with no payload built. nil never reaches the codec: the nil arm is unconditional, so the row stays readable either way.