Dextrin.Binary.Encoder (Dextrin v0.1.0)

Copy Markdown View Source

.dxnb encoder — a recursive walk over CBOR's major types plus a fixed tag dispatch table, matching DXN.md §2.2's table row order.

Hand-rolled rather than built on a generic CBOR library, because .dxnb needs several things no such library is likely to get right by default — each one an integrity concern DXN.md §2.2 calls out explicitly: bignums past 64 bits must use tag 2/3's two's-complement byte form (a generic encoder more easily defaults to a float, or refuses arbitrary-precision integers outright); timestamps/datetimes must use tag 1's integer form, never the float form, which loses microsecond precision at scale; and the private tag block (200–214) plus the value-sharing extension (tags 28/29) aren't things a generic library has any built-in concept of. Given both directions need writing regardless, a direct recursive encoder/decoder pair working over CBOR's major types (0–7) plus a fixed tag table is less code, and less risk, than adapting a general-purpose library to every one of these constraints.

Envelope (magic version cbor_item, DXN.md §2.1) is written once, at encode/2 itself; everything below that is encode_item/2, one recursive function keyed on the Elixir value's own shape.

Determinism is explicitly not a goal: DXN.md doesn't require canonical/deterministic CBOR (no sorted map keys, no shortest-form mandate), and map key order isn't semantically meaningful for DXN's own map type either. So this encoder makes reasonable choices (shortest integer form, no forced key sorting) but two encodings of an equal value aren't guaranteed byte-identical — decode(encode(v)) == v is the contract, not byte-for-byte reproducibility across runs.

Summary

Functions

encode(value, opts \\ [])

@spec encode(
  term(),
  keyword()
) :: {:ok, binary()} | {:error, Dextrin.Error.t()}