.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
@spec encode( term(), keyword() ) :: {:ok, binary()} | {:error, Dextrin.Error.t()}