elm-cardano/cbor

CBOR (RFC 8949) encoder and decoder with lossless round-tripping


Keywords
cbor, elm
License
BSD-3-Clause
Install
elm-package install elm-cardano/cbor 1.0.0

Documentation

elm-cardano/cbor

Fast CBOR (RFC 8949) encoder and decoder for Elm, with lossless round-tripping, and rich error tracking.

Install

elm install elm-cardano/cbor

Quick start

import Cbor.Decode as CD
import Cbor.Encode as CE

-- Encode a string to CBOR bytes
CE.encode (CE.string "hello")

-- Decode CBOR bytes back
CD.decode CD.string someBytes

Encoding

Build encoders from primitives and combinators, then call CE.encode to produce Bytes.

import Cbor exposing (Length(..))
import Cbor.Encode as CE

type alias Person =
    { name : String, age : Int }

encodePerson : Person -> CE.Encoder
encodePerson p =
    CE.keyedRecord CE.Unsorted Definite CE.int
        [ ( 0, Just (CE.string p.name) )
        , ( 1, Just (CE.int p.age) )
        ]

Primitives: int, bigInt, float, bool, null, undefined, string, bytes, maybe

Collections: list, array, map, associativeList, dict, keyedRecord, sequence

Use tagged to wrap any encoder with a semantic tag, intWithWidth / floatWithWidth for explicit wire widths, and stringChunked / bytesChunked for indefinite-length encodings of strings / bytes.

Map sort orders

Pass CE.Unsorted to preserve insertion order, or CE.Sorted toComparable to sort keys. Two predefined orders are provided:

  • CE.deterministicSort -- RFC 8949 Section 4.2.1
  • CE.canonicalSort -- RFC 7049 Section 3.9

Decoding

Build decoders and run them with CD.decode.

import Cbor.Decode as CD

type alias Person =
    { name : String, age : Int }

decodePerson : CD.CborDecoder ctx Person
decodePerson =
    CD.keyedRecord CD.int String.fromInt Person
        |> CD.required 0 CD.string
        |> CD.required 1 CD.int
        |> CD.buildKeyedRecord CD.IgnoreExtra

Primitives: int, bigInt, float, bool, null, undefined, string, bytes, maybe

Collections: array, associativeList, dict, field, foldEntries, tagged

Combinators: map, map2..map5, andThen, oneOf, keep, ignore, lazy, succeed, fail, inContext

Record builders

Three styles for decoding structured data into Elm records:

  • Positional (record / element / buildRecord) -- CBOR arrays where fields are identified by position.
  • Keyed (keyedRecord / required / optional / buildKeyedRecord) -- CBOR maps with known keys, decoded in key order.
  • Unordered (unorderedRecord / onKey / buildUnorderedRecord) -- CBOR maps where keys may appear in any order.

Streaming-style decoding

arrayHeader, mapHeader, break, and untilBreak for manual iteration over CBOR structures. item, itemSkip, and rawBytes as escape hatches.

Error handling

Use inContext to annotate decoders with context labels. On failure, the error includes the label and byte offset, making it easier to locate the problem.

decodePerson : CD.CborDecoder String Person
decodePerson =
    CD.inContext "Person"
        (CD.keyedRecord CD.int String.fromInt Person
            |> CD.required 0 (CD.inContext "name" CD.string)
            |> CD.required 1 (CD.inContext "age" CD.int)
            |> CD.buildKeyedRecord CD.IgnoreExtra
        )

Use errorToString to render errors as human-readable messages:

CD.errorToString contextToString err

Performance

This library is carefully optimized for the Elm runtime. Compared to elm-toulouse/cbor, encoders are 30% to 2x faster, and decoders on the happy path (no failure, no oneOf branching) are 3x to 5x faster. Detailed benchmarks are present in the bench/ folder.

Generic CBOR and diagnostics

The Cbor module exposes CborItem, a lossless representation of any well-formed CBOR encoding. Use Cbor.Decode.item to decode into a CborItem and Cbor.Encode.item to re-encode it -- the round-trip preserves the exact original bytes.

Cbor.diagnose produces the diagnostic notation from RFC 8949 Section 8, useful for debugging and logging:

Cbor.diagnose (CborArray Definite [ CborInt52 IW0 1, CborString "two" ])
-- "[1, \"two\"]"

License

BSD-3-Clause