distribute/codec

Types

pub type DecodeError {
  InvalidBinary(String)
  TypeMismatch(String)
  DecodeFailed(String)
  InsufficientData(String)
  DecodeTimeout
  TagMismatch(expected: String, got: String)
  VersionMismatch(expected: Int, got: Int)
  MigrationMissing(Int)
  MigrationFailed(String)
}

Constructors

  • InvalidBinary(String)

    Binary data is malformed or truncated

  • TypeMismatch(String)

    Decoded value doesn’t match expected type

  • DecodeFailed(String)

    Decoding operation failed

  • InsufficientData(String)

    Binary data is incomplete

  • DecodeTimeout

    Receive timeout when waiting for message

  • TagMismatch(expected: String, got: String)

    Tag mismatch in envelope

  • VersionMismatch(expected: Int, got: Int)

    Version mismatch in envelope

  • MigrationMissing(Int)

    Migration missing for a step (from_version)

  • MigrationFailed(String)

    Migration failed with reason

A Decoder(a) converts binary data into a value of type a. This is the simple decoder that consumes the input without tracking bytes.

pub type Decoder(a) =
  fn(BitArray) -> Result(a, DecodeError)
pub type EncodeError {
  InvalidValue(String)
  EncodeFailed(String)
  ValueTooLarge(String)
}

Constructors

  • InvalidValue(String)

    Value cannot be encoded (e.g., invalid data)

  • EncodeFailed(String)

    Encoding operation failed

  • ValueTooLarge(String)

    Value is too large to encode

An Encoder(a) converts a value of type a to a binary representation.

pub type Encoder(a) =
  fn(a) -> Result(BitArray, EncodeError)

A single-step migration: transforms payload for version from into payload for version from + 1.

pub type Migration =
  #(Int, fn(BitArray) -> Result(BitArray, DecodeError))
pub type MigrationEdge =
  #(Int, Int, fn(BitArray) -> Result(BitArray, DecodeError))

A Schema represents a message type with its tag, version, encoder and decoder. Use this to ensure consistent encoding/decoding across your application.

pub type Schema(a) {
  Schema(
    tag: String,
    version: Int,
    encoder: fn(a) -> Result(BitArray, EncodeError),
    decoder: fn(BitArray) -> Result(a, DecodeError),
  )
}

Constructors

  • Schema(
      tag: String,
      version: Int,
      encoder: fn(a) -> Result(BitArray, EncodeError),
      decoder: fn(BitArray) -> Result(a, DecodeError),
    )

A SizedDecoder(a) converts binary data into a value of type a AND returns the remaining unconsumed bytes. This is essential for composing decoders (lists, tuples, nested structures).

pub type SizedDecoder(a) =
  fn(BitArray) -> Result(#(a, BitArray), DecodeError)

Values

pub fn any_encoder() -> fn(a) -> Result(BitArray, EncodeError)

Encode any Gleam value to binary using Erlang term serialization.

⚠️ ESCAPE HATCH — Bypasses type safety. See dynamic_encoder warnings. This is a convenience function for quick prototyping.

pub fn bitarray_decoder() -> fn(BitArray) -> Result(
  BitArray,
  DecodeError,
)

BitArray decoder: simple decoder.

pub fn bitarray_encoder() -> fn(BitArray) -> Result(
  BitArray,
  EncodeError,
)

BitArray encoder: 32-bit length prefix followed by raw bytes.

pub fn bitarray_sized_decoder() -> fn(BitArray) -> Result(
  #(BitArray, BitArray),
  DecodeError,
)

BitArray sized decoder: returns decoded bytes and remaining.

pub fn bool_decoder() -> fn(BitArray) -> Result(Bool, DecodeError)

Boolean decoder: simple decoder.

pub fn bool_encoder() -> fn(Bool) -> Result(BitArray, EncodeError)

Boolean encoder: single byte (0 or 1).

pub fn bool_sized_decoder() -> fn(BitArray) -> Result(
  #(Bool, BitArray),
  DecodeError,
)

Boolean sized decoder: returns decoded bool and remaining bytes.

pub fn build_migration_chain(
  migrations: List(
    #(Int, fn(BitArray) -> Result(BitArray, DecodeError)),
  ),
) -> fn(Int, Int, BitArray) -> Result(BitArray, DecodeError)

Build a migration chain from single-step migrations.

The returned function has signature: fn(from_version, to_version, payload) and applies the sequence of migrations (from->from+1, … -> to) if possible. If a step is missing, returns MigrationMissing(step).

pub fn build_migration_graph(
  edges: List(
    #(Int, Int, fn(BitArray) -> Result(BitArray, DecodeError)),
  ),
) -> fn(Int, Int, BitArray) -> Result(BitArray, DecodeError)

Build a migration graph from a list of migration edges. Each edge is a single migration from from -> to (not necessarily +1). The returned function will find a path from from to to (if any) and apply each edge’s migration in sequence. If no path exists, returns MigrationMissing(step) where step is the first missing intermediate version.

pub fn decode(
  decoder: fn(BitArray) -> Result(a, DecodeError),
  data: BitArray,
) -> Result(a, DecodeError)

Decode binary data using the given decoder.

pub fn decode_error_to_string(error: DecodeError) -> String

Format a DecodeError as a human-readable string.

pub fn decode_sized(
  decoder: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  data: BitArray,
) -> Result(#(a, BitArray), DecodeError)

Decode binary data using a sized decoder, returning value and remaining bytes.

pub fn dynamic_decoder() -> fn(BitArray) -> Result(
  dynamic.Dynamic,
  DecodeError,
)

Decoder for any Gleam/Erlang term using binary_to_term.

⚠️ ESCAPE HATCH — This decoder bypasses Gleam’s type system. The returned Dynamic must be validated using gleam/dynamic decoders. Binary data from untrusted sources should be treated with extreme caution.

Uses binary_to_term([safe]) to prevent atom table attacks, but the decoded value still requires runtime type checking.

pub fn dynamic_encoder() -> fn(dynamic.Dynamic) -> Result(
  BitArray,
  EncodeError,
)

Encoder for any Gleam/Erlang term using term_to_binary.

⚠️ ESCAPE HATCH — This encoder bypasses Gleam’s type system. The binary format is opaque and may not be portable across:

  • Different Erlang/OTP versions
  • Different Gleam versions
  • Type definition changes in your code

Use typed codecs (string_encoder, int_encoder, etc.) whenever possible. This is suitable only for temporary in-cluster communication or prototyping.

pub fn encode(
  encoder: fn(a) -> Result(BitArray, EncodeError),
  value: a,
) -> Result(BitArray, EncodeError)

Encode a value using the given encoder.

pub fn encode_error_to_string(error: EncodeError) -> String

Format an EncodeError as a human-readable string.

pub fn float_decoder() -> fn(BitArray) -> Result(
  Float,
  DecodeError,
)

Float decoder: simple decoder.

pub fn float_encoder() -> fn(Float) -> Result(
  BitArray,
  EncodeError,
)

Float encoder: 64-bit IEEE 754 encoding (8 bytes).

pub fn float_sized_decoder() -> fn(BitArray) -> Result(
  #(Float, BitArray),
  DecodeError,
)

Float sized decoder: returns decoded float and remaining bytes.

pub fn int_decoder() -> fn(BitArray) -> Result(Int, DecodeError)

Integer decoder: simple decoder.

pub fn int_encoder() -> fn(Int) -> Result(BitArray, EncodeError)

Integer encoder: 64-bit big-endian encoding (8 bytes).

pub fn int_sized_decoder() -> fn(BitArray) -> Result(
  #(Int, BitArray),
  DecodeError,
)

Integer sized decoder: returns decoded int and remaining bytes.

pub fn list_decoder(
  element_decoder: fn(BitArray) -> Result(
    #(a, BitArray),
    DecodeError,
  ),
) -> fn(BitArray) -> Result(List(a), DecodeError)

List decoder using a SizedDecoder for elements.

pub fn list_decoder_fixed(
  element_decoder: fn(BitArray) -> Result(a, DecodeError),
) -> fn(BitArray) -> Result(List(a), DecodeError)

Deprecated: Use list_decoder with a SizedDecoder for proper byte tracking.

List decoder using a simple Decoder - ONLY for fixed-size elements.

⚠️ WARNING: This function only works correctly with fixed-size decoders (e.g., int_decoder, float_decoder, bool_decoder). For variable-size elements (strings, nested lists, custom types), use list_decoder with a SizedDecoder.

pub fn list_encoder(
  element_encoder: fn(a) -> Result(BitArray, EncodeError),
) -> fn(List(a)) -> Result(BitArray, EncodeError)

List encoder: 16-bit length prefix followed by encoded elements.

pub fn list_sized_decoder(
  element_decoder: fn(BitArray) -> Result(
    #(a, BitArray),
    DecodeError,
  ),
) -> fn(BitArray) -> Result(#(List(a), BitArray), DecodeError)

List sized decoder using a SizedDecoder for elements. This properly tracks byte consumption for each element.

pub fn new_schema(
  tag tag: String,
  version version: Int,
  encoder encoder: fn(a) -> Result(BitArray, EncodeError),
  decoder decoder: fn(BitArray) -> Result(a, DecodeError),
) -> Schema(a)

Create a new schema with the given tag, version, encoder, and decoder.

pub fn option_decoder(
  inner: fn(BitArray) -> Result(a, DecodeError),
) -> fn(BitArray) -> Result(option.Option(a), DecodeError)

Option decoder (simple).

pub fn option_encoder(
  inner: fn(a) -> Result(BitArray, EncodeError),
) -> fn(option.Option(a)) -> Result(BitArray, EncodeError)

Codec for Option(a).

pub fn option_sized_decoder(
  inner: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
) -> fn(BitArray) -> Result(
  #(option.Option(a), BitArray),
  DecodeError,
)

Option sized decoder.

pub fn peek_envelope(
  data: BitArray,
) -> Result(#(String, Int), DecodeError)

Get tag and version from a binary envelope without decoding payload.

pub fn peek_tag(data: BitArray) -> Result(String, DecodeError)

Get the tag from a binary envelope without full decode.

pub fn pid_decoder() -> fn(BitArray) -> Result(
  process.Pid,
  DecodeError,
)

Decoder for Pids.

Note: The decoded Pid is validated as a proper Erlang pid internally.

pub fn pid_encoder() -> fn(process.Pid) -> Result(
  BitArray,
  EncodeError,
)

Encoder for Pids.

Note: Pids are inherently untyped in Erlang. This encoder uses term_to_binary which is safe for same-cluster communication.

pub fn receive_with_decoder(
  decoder: fn(BitArray) -> Result(a, DecodeError),
  expected_tag: String,
  expected_version: Int,
  data: BitArray,
) -> Result(a, DecodeError)
pub fn result_decoder(
  ok_decoder: fn(BitArray) -> Result(a, DecodeError),
  error_decoder: fn(BitArray) -> Result(e, DecodeError),
) -> fn(BitArray) -> Result(Result(a, e), DecodeError)

Result decoder (simple).

pub fn result_encoder(
  ok_encoder: fn(a) -> Result(BitArray, EncodeError),
  error_encoder: fn(e) -> Result(BitArray, EncodeError),
) -> fn(Result(a, e)) -> Result(BitArray, EncodeError)

Codec for Result(a, e).

pub fn result_sized_decoder(
  ok_decoder: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  error_decoder: fn(BitArray) -> Result(
    #(e, BitArray),
    DecodeError,
  ),
) -> fn(BitArray) -> Result(
  #(Result(a, e), BitArray),
  DecodeError,
)

Result sized decoder.

pub fn schema_decode(
  schema: Schema(a),
  data: BitArray,
) -> Result(a, DecodeError)

Decode a value using a schema. Validates tag and version before decoding.

pub fn schema_decode_with_migrations(
  target_schema: Schema(a),
  migrations: List(
    #(Int, fn(BitArray) -> Result(BitArray, DecodeError)),
  ),
) -> fn(BitArray) -> Result(a, DecodeError)

Decode a schema applying migrations when necessary.

  • target_schema is the desired schema to decode into (its decoder is used).
  • migrations is a list of pairs (version, migr_fn) where migr_fn transforms an older payload into the payload expected by the target schema.

If a payload arrives with an older version, the corresponding migration is applied before decoding. If no migration is found the decoder returns VersionMismatch.

pub fn schema_encode(
  schema: Schema(a),
  value: a,
) -> Result(BitArray, EncodeError)

Encode a value using a schema. Automatically wraps in envelope with tag+version.

pub fn schema_matches_tag(
  schema: Schema(a),
  data: BitArray,
) -> Bool

Check if binary data matches a schema’s tag (without full decode). Useful for routing messages to the correct handler.

pub fn string_decoder() -> fn(BitArray) -> Result(
  String,
  DecodeError,
)

String decoder: simple decoder that discards remaining bytes.

pub fn string_encoder() -> fn(String) -> Result(
  BitArray,
  EncodeError,
)

String encoder: UTF-8 encoding with 16-bit length prefix.

pub fn string_sized_decoder() -> fn(BitArray) -> Result(
  #(String, BitArray),
  DecodeError,
)

String sized decoder: returns decoded string and remaining bytes.

pub fn subject_decoder() -> fn(BitArray) -> Result(
  process.Subject(a),
  DecodeError,
)

Decoder for Subjects.

⚠️ Type parameter is not validated — The returned Subject(a) will accept any type parameter at compile time. Ensure the type matches what was encoded, or use Schema-based messaging for safety.

pub fn subject_encoder() -> fn(process.Subject(a)) -> Result(
  BitArray,
  EncodeError,
)

Encoder for Subjects.

Note: Subject serialization preserves the Pid but the type parameter is erased. The receiving side must know the expected message type.

pub fn to_decoder(
  sized: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
) -> fn(BitArray) -> Result(a, DecodeError)

Convert a SizedDecoder to a simple Decoder (discards remaining bytes).

pub fn tuple2_decoder(
  first: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  second: fn(BitArray) -> Result(#(b, BitArray), DecodeError),
) -> fn(BitArray) -> Result(#(a, b), DecodeError)

Simple decoder for 2-tuples. Expects to consume most of the input.

Example

let dec = codec.tuple2_decoder(
  codec.int_sized_decoder(),
  codec.string_sized_decoder()
)
let Ok(#(num, text)) = codec.decode(dec, encoded)
pub fn tuple2_encoder(
  first: fn(a) -> Result(BitArray, EncodeError),
  second: fn(b) -> Result(BitArray, EncodeError),
) -> fn(#(a, b)) -> Result(BitArray, EncodeError)

Encoder for 2-tuples. Encodes both elements with length prefix for first.

Example

let enc = codec.tuple2_encoder(codec.int_encoder(), codec.string_encoder())
let encoded = codec.encode(enc, #(42, "hello"))
pub fn tuple2_sized_decoder(
  first: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  second: fn(BitArray) -> Result(#(b, BitArray), DecodeError),
) -> fn(BitArray) -> Result(#(#(a, b), BitArray), DecodeError)

SizedDecoder for 2-tuples. Returns the decoded tuple and remaining bytes. Use with to_decoder() for simple decoding.

pub fn tuple3_decoder(
  first: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  second: fn(BitArray) -> Result(#(b, BitArray), DecodeError),
  third: fn(BitArray) -> Result(#(c, BitArray), DecodeError),
) -> fn(BitArray) -> Result(#(a, b, c), DecodeError)

Tuple3 decoder (simple).

pub fn tuple3_encoder(
  first: fn(a) -> Result(BitArray, EncodeError),
  second: fn(b) -> Result(BitArray, EncodeError),
  third: fn(c) -> Result(BitArray, EncodeError),
) -> fn(#(a, b, c)) -> Result(BitArray, EncodeError)

Encoder for 3-tuples. Encodes all three elements with length prefixes.

pub fn tuple3_sized_decoder(
  first: fn(BitArray) -> Result(#(a, BitArray), DecodeError),
  second: fn(BitArray) -> Result(#(b, BitArray), DecodeError),
  third: fn(BitArray) -> Result(#(c, BitArray), DecodeError),
) -> fn(BitArray) -> Result(#(#(a, b, c), BitArray), DecodeError)

Tuple3 sized decoder.

pub fn unwrap_envelope(
  data: BitArray,
) -> Result(#(String, Int, BitArray), DecodeError)

Unwrap an envelope, returning the tag, version, and payload.

Returns DecodeError if the envelope format is invalid.

Example

case codec.unwrap_envelope(data) {
  Ok(#(tag, version, payload)) -> // Process message
  Error(e) -> // Handle error
}
pub fn versioned_decoder(
  tag: String,
  handlers: List(#(Int, fn(BitArray) -> Result(a, DecodeError))),
) -> fn(BitArray) -> Result(a, DecodeError)

Create a versioned migration path between schema versions. Returns a decoder that can handle multiple versions.

pub fn versioned_decoder_from_schemas(
  schemas: List(Schema(a)),
) -> fn(BitArray) -> Result(a, DecodeError)

Build a versioned decoder from a list of Schema(a). All schemas must share the same tag. Returns a decoder that picks the appropriate schema by version and decodes the payload.

pub fn wrap_envelope(
  tag: String,
  version: Int,
  payload: BitArray,
) -> BitArray

Wrap a payload with an envelope containing a protocol tag and version.

The envelope format is: [tag_len:16][tag:utf8][version:32][payload]

This enables protocol mismatch detection and versioned message handling.

Example

let payload = codec.encode(codec.string_encoder(), "hello")
let envelope = codec.wrap_envelope("my_protocol", 1, payload |> result.unwrap(<<>>))
Search Document