StatifierUI.Trace.Message (StatifierUI v0.1.0)

Copy Markdown View Source

One message of the trace wire format (docs/wire-format.md), held as a struct in process and rendered to the documented JSON object by to_map/1.

The envelope fields (type, session, seq, and the counters macrostep/microstep/round, carried by trace.* and effect.* messages and by no session.* message) live on the struct directly; everything type-specific lives in payload, already in wire shape - a string-keyed map ready to merge over the envelope. This is decision 1 of the plan: one envelope struct rather than one struct per message type, so the spec stays the single definition site of what a payload contains.

A payload key is never allowed to collide with an envelope key - that would be a producer bug, silently resolved by whichever side to_map/1 happened to favor. validate/1 catches it explicitly; StatifierUI.Trace.Normalizer calls it on every message it builds, so by the time anything else sees a %Message{} the invariant already holds and to_map/1 can be specced for a valid message.

Summary

Types

A JSON-ready term - the codomain every payload value must land in.

t()

Functions

Renders message to the documented JSON object: the envelope merged over the payload.

Checks that message's payload carries no key reserved for the envelope.

Types

json()

@type json() ::
  nil
  | boolean()
  | integer()
  | float()
  | String.t()
  | [json()]
  | %{optional(String.t()) => json()}

A JSON-ready term - the codomain every payload value must land in.

t()

@type t() :: %StatifierUI.Trace.Message{
  macrostep: non_neg_integer() | nil,
  microstep: non_neg_integer() | nil,
  payload: %{optional(String.t()) => json()},
  round: non_neg_integer() | nil,
  seq: non_neg_integer(),
  session: String.t(),
  type: String.t()
}

Functions

to_map(message)

@spec to_map(t()) :: %{optional(String.t()) => json()}

Renders message to the documented JSON object: the envelope merged over the payload.

"type", "session", and "seq" are always present; "macrostep", "microstep", and "round" are present only when non-nil (decisions 3 and 5, amended by sui-67d - trace.* and effect.* messages carry all three, session.* messages carry none of the three). Specced for a valid message: call validate/1 first, or construct through StatifierUI.Trace.Normalizer, which always does.

validate(message)

@spec validate(t()) :: {:ok, t()} | {:error, {:reserved_payload_key, String.t()}}

Checks that message's payload carries no key reserved for the envelope.

Returns {:error, {:reserved_payload_key, key}} naming the first colliding key found, rather than letting the envelope silently win in to_map/1. Called by StatifierUI.Trace.Normalizer on every message it builds, so any other caller can treat a %Message{} it receives as already valid.

Examples

iex> message = %StatifierUI.Trace.Message{type: "effect.log", session: "s", seq: 0}
iex> StatifierUI.Trace.Message.validate(message)
{:ok, message}