Omni.Codec (Omni v1.3.2)

Copy Markdown View Source

Lossless serialisation of Omni structs to and from JSON-safe maps.

Use this when persisting messages, content blocks, or usage records to a storage layer that speaks JSON (Ecto :map columns, document stores, the wire). encode/1 produces a plain map (or list of maps) with string keys and only JSON-compatible values; decode/1 reverses it, returning the original structs.

The encoded shape is self-describing — every encoded value carries a "__type" discriminator so decode/1 can dispatch without external context.

Supported types

Lists of any combination of the above are also supported.

Opaque fields

Message.private and Attachment.meta can hold arbitrary terms (atoms, tuples, structs from provider-specific data) that have no portable JSON representation. These are encoded as opaque base64-encoded ETF blobs (%{"__etf" => "..."}) and decoded with the :safe option, which refuses to create new atoms or load resources.

Encoding arbitrary terms

encode_term/1 and decode_term/1 expose the same opaque-blob mechanism for any Erlang term. Use them in persistence layers that need to stash values outside the Omni struct family while keeping the same JSON-safe shape.

Examples

iex> message = Omni.Message.new("hello")
iex> encoded = Omni.Codec.encode(message)
iex> {:ok, ^message} = Omni.Codec.decode(encoded)

Summary

Types

Any value the codec can encode.

Decode error reasons.

Functions

Decodes a previously-encoded map (or list of maps) back into Omni structs.

Decodes a wrapper produced by encode_term/1 back into the original term.

Encodes an Omni struct (or a list of structs) to a JSON-safe map.

Encodes an arbitrary Erlang term as an opaque JSON-safe wrapper.

Types

encodable()

Any value the codec can encode.

error()

@type error() ::
  :invalid_input
  | {:unknown_type, String.t()}
  | {:invalid_role, term()}
  | {:missing_field, atom()}
  | {:invalid_source, term()}
  | {:invalid_timestamp, term()}
  | {:invalid_etf, term()}

Decode error reasons.

Functions

decode(list)

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

Decodes a previously-encoded map (or list of maps) back into Omni structs.

Returns {:ok, struct} or {:ok, [struct, ...]} on success. Returns {:error, reason} if the input is malformed, has an unknown "__type", or fails field-level validation. For lists, decoding stops on the first failing element.

decode_term(other)

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

Decodes a wrapper produced by encode_term/1 back into the original term.

Uses :erlang.binary_to_term/2 with the :safe option, so unknown atoms are not created and code is not loaded from the binary.

encode(list)

@spec encode(encodable() | [encodable()]) :: map() | [map()]

Encodes an Omni struct (or a list of structs) to a JSON-safe map.

Always succeeds for valid input types. Lists are encoded element-wise and returned as a bare list (no envelope).

encode_term(term)

@spec encode_term(term()) :: %{required(String.t()) => String.t()}

Encodes an arbitrary Erlang term as an opaque JSON-safe wrapper.

The term is serialised via :erlang.term_to_binary/1 and base64-encoded inside a %{"__etf" => "..."} map. Use this for values that have no portable JSON representation (atom-keyed maps, tuples, structs) when you need to stash them in a JSON-typed storage column.

iex> wrapper = Omni.Codec.encode_term({:ok, %{a: 1}})
iex> {:ok, {:ok, %{a: 1}}} = Omni.Codec.decode_term(wrapper)