Dextrin (Dextrin v0.1.0)

Copy Markdown View Source

Public API for DXN (Data eXchange Notation) — .dxn text and .dxnb binary, per DXN.md (the normative format specification).

Four functions, one shared error type (Dextrin.Error):

Every DXN type decodes to a plain Elixir value where one exists (integer, float, list, a plain map, ...) and to a small wrapper struct where Elixir has nothing that fits without losing information (Dextrin.Symbol, Dextrin.Tuple, Dextrin.OrderedMap, ...) — see each value module under Dextrin.Value for the full list. symbol and keyword in particular always wrap a String.t(), never an Elixir atom: decoding untrusted DXN data can never be used to exhaust the atom table.

struct is schema-dependent: without a compiled .dxns schema for a given name it decodes to an opaque Dextrin.Struct; with one (Dextrin.Schema.compile/3, passed in as registry:), field enforcement happens automatically in both decode functions, and a violation is an ordinary {:error, %Dextrin.Error{}}, never a raised exception. encode/2/encode_binary/2 validate the other direction the same way, automatically — see encode/2's own doc.

.dxnb's private CBOR tag block (200–214, mirrored in Dextrin.Binary.Tags) is not IANA-registered — collision-free only within dextrin-produced documents. Don't assume interop with some other CBOR-based format that happens to also use a tag in that range; it isn't reserved for DXN outside this library's own output.

Summary

Functions

Decodes .dxn text into a value. {:error, _} carries a single Dextrin.Error normally, but a list when the underlying grammar engine reports more than one (Ichor.Actions.evaluate/5's own Ichor.Error.t() | [Ichor.Error.t()]).

Decodes a .dxnb binary into a value. Same trusted: opt as decode/2 — see there for what it does and doesn't affect.

Encodes a value back to .dxn text.

Encodes a value to .dxnb binary. Same schema:/validate: opts as encode/2.

Types

opts()

@type opts() :: [
  registry: Dextrin.Registry.t(),
  schema: String.t(),
  validate: boolean(),
  pretty: boolean(),
  indent: non_neg_integer(),
  trusted: boolean()
]

Functions

decode(text, opts \\ [])

@spec decode(String.t(), opts()) ::
  {:ok, term()} | {:error, Dextrin.Error.t() | [Dextrin.Error.t()]}

Decodes .dxn text into a value. {:error, _} carries a single Dextrin.Error normally, but a list when the underlying grammar engine reports more than one (Ichor.Actions.evaluate/5's own Ichor.Error.t() | [Ichor.Error.t()]).

Decodes keyword as a real Elixir atom by default (trusted: true) — see Dextrin.Registry.put_trusted/2's own doc for exactly what this does and doesn't affect. Pass trusted: false for any source you don't fully control; the default assumes a source you do (your own config, your own application's data, ...), not arbitrary untrusted/network input, where an unbounded String.to_atom/1 could exhaust the atom table.

decode_binary(bytes, opts \\ [])

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

Decodes a .dxnb binary into a value. Same trusted: opt as decode/2 — see there for what it does and doesn't affect.

encode(value, opts \\ [])

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

Encodes a value back to .dxn text.

Single-line, minimal-whitespace by default — the smallest text this value can round-trip through, with no line-wrapping or indentation at all (Dextrin.Text.Printer). Pass pretty: true for multi-line, indented output instead (Dextrin.Text.Formatter) — indent: then sets the number of spaces per nesting level (default 2). Both produce the exact same value on the way back through decode/2; pretty/indent are a rendering choice, never a semantic one.

{:ok, value} = Dextrin.decode("%{x: 1, y: 2}")

Dextrin.encode(value)
#=> {:ok, "%{x:1,y:2}"}

Dextrin.encode(value, pretty: true)
#=> {:ok, "%{\n  x: 1\n  y: 2\n}"}

Dextrin.encode(value, pretty: true, indent: 4)
#=> {:ok, "%{\n    x: 1\n    y: 2\n}"}

Automatically validates every Dextrin.Struct or registered application struct found anywhere in value against its own schema (Dextrin.Schema.validate_encode_tree/2), the same way decoding checks every named struct unconditionally — set validate: false to skip this (e.g. deliberately encoding data that doesn't conform, for a test fixture or a pass-through/relay that shouldn't second-guess data it isn't the origin of). A schema: opt additionally validates value itself against that specific schema (Dextrin.Schema.validate_encode/3) — the one case the automatic walk can't cover on its own, a nameless plain map or struct at the very top. Either check failing returns {:error, _} instead of encoding a value that doesn't conform — regardless of pretty:, since validation and rendering are independent concerns.

encode_binary(value, opts \\ [])

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

Encodes a value to .dxnb binary. Same schema:/validate: opts as encode/2.