Spectre.Run.Value (Spectre v0.3.0)

Copy Markdown View Source

Portable value codec for run state.

Values that cross a run boundary must survive serialization and restarts, so they cannot contain PIDs, ports, references or functions. This module validates that constraint, encodes values into JSON-safe tagged maps (atoms, tuples, structs and non-string-keyed maps get a "$spectre" tag), decodes them back, and derives deterministic identifiers from them.

Summary

Functions

Decodes a value produced by encode/1 back into its original shape.

Encodes a portable value into its JSON-safe representation.

Derives a stable identifier from value, or nil when it cannot.

Derives a stable identifier from value, keeping validation errors.

Preloads the modules referenced by an encoded value before decoding.

Builds a "prefix:hash" token from a deterministic digest of value.

Checks that value is portable run data.

Functions

decode(value)

@spec decode(term()) :: {:ok, term()} | {:error, term()}

Decodes a value produced by encode/1 back into its original shape.

Atoms and struct modules are resolved with String.to_existing_atom/1 only — unknown names return an error instead of creating atoms.

encode(value)

@spec encode(term()) :: {:ok, term()} | {:error, term()}

Encodes a portable value into its JSON-safe representation.

Primitives pass through unchanged; atoms, tuples, structs and maps with non-string keys become tagged maps that decode/1 can reverse.

logical_id(value, prefix \\ "logical")

@spec logical_id(term(), String.t()) :: String.t() | nil

Derives a stable identifier from value, or nil when it cannot.

Non-empty strings are used as-is; any other portable value is hashed via token/2. Non-portable values yield nil rather than an error.

opaque_id(value, prefix \\ "subject")

@spec opaque_id(term(), String.t()) :: {:ok, String.t() | nil} | {:error, term()}

Derives a stable identifier from value, keeping validation errors.

Unlike logical_id/2, a non-portable value returns the underlying {:error, reason} so callers can surface it.

prepare(value)

@spec prepare(term()) :: :ok | {:error, term()}

Preloads the modules referenced by an encoded value before decoding.

Struct tags require their module to exist and load; module-shaped atom tags are loaded opportunistically so decode/1 can resolve them as existing atoms.

token(prefix, value)

@spec token(String.t(), term()) :: String.t()

Builds a "prefix:hash" token from a deterministic digest of value.

The same value always produces the same token, so tokens are safe to use as cross-restart identifiers.

validate(value, path \\ [])

@spec validate(term(), [term()]) :: :ok | {:error, term()}

Checks that value is portable run data.

Returns {:error, {:nonportable_run_value, path, kind}} when the value — at any depth — contains a PID, port, reference, function or improper list.