elm-cardano/cbor/1.0.0

Cbor.Decode

CBOR decoding combinators.

Build decoders for your domain types. These produce CborDecoder values. Run them with 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


-- To run: CD.decode decodePerson someBytes

Note: These decoders operate on a complete Bytes value. They cannot incrementally decode a stream of chunks as they arrive. If you are receiving CBOR data in multiple chunks (e.g. from a network stream), you must first concatenate all chunks into a single Bytes value before calling decode. If incremental / streaming decoding becomes necessary, it can be added in a future release.

Type

type CborDecoder ctx a

Opaque CBOR decoder.

Internally uses two variants: Item receives a pre-read initial byte (the CBOR byte encoding major type + additional info), and Pure consumes no initial byte (used only by succeed and fail). This split lets combinators route the initial byte to the first decoder that needs it, and lets collection decoders detect indefinite-length break bytes on the fast path without BD.oneOf.

decode : CborDecoder ctx a -> Bytes -> Result (Error ctx) a

Run a CborDecoder on some Bytes.

Errors

errorToString : (context -> String) -> Error context -> String

Convert an Error to a human-readable string.

Provide a function to display context labels. If your decoder does not use inContext, you can pass always "".

Combinators

succeed : a -> CborDecoder ctx a

A decoder that always succeeds with the given value, consuming no bytes.

A decoder that always fails with the given error.

map : (a -> b) -> CborDecoder ctx a -> CborDecoder ctx b

Transform the result of a decoder.

(a -> b -> c)
-> CborDecoder ctx a
-> CborDecoder ctx b
-> CborDecoder ctx c

Combine two decoders sequentially.

(a -> b -> c -> d)
-> CborDecoder ctx a
-> CborDecoder ctx b
-> CborDecoder ctx c
-> CborDecoder ctx d

Combine three decoders sequentially.

(a -> b -> c -> d -> e)
-> CborDecoder ctx a
-> CborDecoder ctx b
-> CborDecoder ctx c
-> CborDecoder ctx d
-> CborDecoder ctx e

Combine four decoders sequentially.

(a -> b -> c -> d -> e -> f)
-> CborDecoder ctx a
-> CborDecoder ctx b
-> CborDecoder ctx c
-> CborDecoder ctx d
-> CborDecoder ctx e
-> CborDecoder ctx f

Combine five decoders sequentially.

andThen : (a -> CborDecoder ctx b) -> CborDecoder ctx a -> CborDecoder ctx b

Chain decoders. The continuation is chosen based on the first result.

oneOf : List (CborDecoder ctx a) -> CborDecoder ctx a

Try multiple decoders in order. Each decoder sees the same initial byte.

keep : CborDecoder ctx a -> CborDecoder ctx (a -> b) -> CborDecoder ctx b

Apply a decoder producing a function to a decoder producing an argument.

succeed Point
    |> keep float
    |> keep float
CborDecoder ctx ignored
-> CborDecoder ctx keep
-> CborDecoder ctx keep

Decode and discard a value, keeping the previous result.

lazy : (() -> CborDecoder ctx a) -> CborDecoder ctx a

Deferred construction for recursive decoders.

inContext : ctx -> CborDecoder ctx a -> CborDecoder ctx a

Add context to a decoder for better error messages.

If the decoder fails, the error will be wrapped with the given label and the byte offset where decoding started.

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
        )

Primitives

int : CborDecoder ctx Int

Decode a CBOR integer (major types 0 and 1).

Fails if the absolute value exceeds 2^52. For larger values, use bigInt.

bigInt : CborDecoder ctx ( Sign, Bytes )

Decode any CBOR integer, including bignums (tags 2 and 3).

Returns ( Sign, Bytes ) where Bytes is the minimal big-endian representation of the unsigned argument.

float : CborDecoder ctx Float

Decode a CBOR float (major type 7, additional info 25/26/27).

bool : CborDecoder ctx Bool

Decode a CBOR boolean (0xF4 for false, 0xF5 for true).

null : a -> CborDecoder ctx a

Decode a CBOR null (0xF6), returning the provided default value.

undefined : a -> CborDecoder ctx a

Decode a CBOR undefined (0xF7), returning the provided default value.

maybe : CborDecoder ctx a -> CborDecoder ctx (Maybe a)

Decode a nullable value: CBOR null (0xF6) or undefined (0xF7) become Nothing, anything else is decoded with the inner decoder and wrapped in Just.

Decode a CBOR simple value (major type 7).

Returns the simple value number. Rejects booleans (20–21), null (22), undefined (23), and floats (25–27) which have their own decoders.

string : CborDecoder ctx String

Decode a CBOR text string (major type 3).

Concatenates indefinite-length chunks transparently.

bytes : CborDecoder ctx Bytes

Decode a CBOR byte string (major type 2).

Concatenates indefinite-length chunks transparently.

Collections

array : CborDecoder ctx a -> CborDecoder ctx (List a)

Decode a CBOR array (major type 4) where all elements use the same decoder.

Handles both definite and indefinite-length arrays. The element decoder must be an Item (i.e. consume bytes); using succeed or fail directly as an element decoder will produce a NonConsumingDecoder error.

-> CborDecoder ctx v
-> CborDecoder ctx (List ( k, v ))

Decode a CBOR map (major type 5) as a list of key-value pairs.

Handles both definite and indefinite-length maps.

CborDecoder ctx comparable
-> CborDecoder ctx v
-> CborDecoder ctx (Dict comparable v)

Decode a CBOR map (major type 5) into a Dict.

The key decoder must produce comparable values.

k
-> (k -> String)
-> CborDecoder ctx k
-> CborDecoder ctx v
-> CborDecoder ctx v

Decode the next map entry, expecting a specific key.

Reads the key, compares via ==, then decodes the value on match. Fails on mismatch.

-> (k -> acc -> CborDecoder ctx acc)
-> acc
-> CborDecoder ctx acc

Fold over map entries with a handler for each key.

Reads the map header, then loops through entries. For each entry: decode the key, call the handler with the key and current accumulator. The handler decodes the value and returns the updated accumulator.

Important: the handler MUST decode exactly one value per call.

handler key acc =
    case key of
        0 ->
            map (\name -> { acc | name = name }) string


        1 ->
            map (\age -> { acc | age = age }) int


        _ ->
            map (\_ -> acc) itemSkip

Decode a CBOR tag (major type 6), returning the Tag value.

Does not decode the enclosed item. Useful with andThen to branch on the tag number and then decode the body accordingly.

tagged : Tag -> CborDecoder ctx a -> CborDecoder ctx a

Decode a tagged CBOR value (major type 6).

Expects a specific tag, then decodes the enclosed item.

Record Builder (CBOR arrays → Elm values)

type RecordBuilder ctx a

Opaque builder for decoding CBOR arrays into Elm values.

Uses SimpleBuilder when all fields are element (fast path via keep), or CountedBuilder when optionalElement is used (counts remaining items).

record : a -> RecordBuilder ctx a

Start building a record decoder with the constructor function.

record Point
    |> element float
    |> element float
    |> buildRecord IgnoreExtra
-> RecordBuilder ctx (v -> a)
-> RecordBuilder ctx a

Decode one array element, applying it to the constructor.

-> v
-> RecordBuilder ctx (v -> a)
-> RecordBuilder ctx a

Decode an optional array element, using the default if no items remain.

Optional elements must be at the end of the array.

= IgnoreExtra
| FailOnExtra

What to do when the CBOR array has more elements than the builder expects.

  • IgnoreExtra — silently skip trailing elements (forward-compatible).
  • FailOnExtra — fail with TooManyElements (strict validation).

Finalize a record builder into a decoder.

Reads the array header, runs the builder pipeline, and handles any extra elements according to the ExtraElements strategy.

Keyed Record Builder (CBOR maps → Elm values)

type KeyedRecordBuilder ctx k a

Opaque builder for decoding CBOR maps into Elm values.

keyedRecord : CborDecoder ctx k -> (k -> String) -> a -> KeyedRecordBuilder ctx k a

Start building a keyed record decoder.

keyedRecord int String.fromInt Person
    |> required 0 string
    |> required 1 int
    |> buildKeyedRecord IgnoreExtra
k
-> CborDecoder ctx v
-> KeyedRecordBuilder ctx k (v -> a)
-> KeyedRecordBuilder ctx k a

Decode a required map field by key.

The key order in the builder must match the key order in the CBOR data.

k
-> CborDecoder ctx v
-> v
-> KeyedRecordBuilder ctx k (v -> a)
-> KeyedRecordBuilder ctx k a

Decode an optional map field by key.

If the next key doesn't match, the key is stashed for the next step and the default value is used.

Finalize a keyed record builder into a decoder.

Reads the map header, runs the builder pipeline, and verifies all entries were consumed.

Unordered Record Builder (CBOR maps → Elm values, any key order)

type UnorderedRecordBuilder ctx comparable acc

Builder for decoding CBOR maps into records regardless of key order.

Unlike KeyedRecordBuilder which requires keys in the declared order, this builder uses a Dict to dispatch on each key. Unknown keys are handled according to the ExtraElements policy.

The trade-off: the caller must define an accumulator type and a finalize function, since the constructor-threading pattern requires ordered application.

CborDecoder ctx comparable
-> acc
-> UnorderedRecordBuilder ctx comparable acc

Start building an unordered record decoder.

type alias PersonAcc =
    { name : Maybe String, age : Maybe Int }


decodePerson : CborDecoder ctx Person
decodePerson =
    unorderedRecord int { name = Nothing, age = Nothing }
        |> onKey 0 string (\v acc -> { acc | name = Just v })
        |> onKey 1 int (\v acc -> { acc | age = Just v })
        |> buildUnorderedRecord IgnoreExtra
            (\acc -> Maybe.map2 Person acc.name acc.age)
comparable
-> CborDecoder ctx v
-> (v -> acc -> acc)
-> UnorderedRecordBuilder ctx comparable acc
-> UnorderedRecordBuilder ctx comparable acc

Register a field handler for a specific key.

When the key is encountered during decoding, the value decoder runs and the setter updates the accumulator.

-> (acc -> Maybe a)
-> UnorderedRecordBuilder ctx comparable acc
-> CborDecoder ctx a

Finalize an unordered record builder into a decoder.

Reads the map header, scans all entries dispatching to registered handlers, then calls the finalize function. If finalize returns Nothing, decoding fails with TooFewElements.

Note: uses O(N) stack depth where N is the number of map entries. Safe for typical CBOR maps (up to a few hundred entries).

Structure Headers

arrayHeader : CborDecoder ctx (Maybe Int)

Decode a CBOR array header, returning the count or Nothing for indefinite-length.

mapHeader : CborDecoder ctx (Maybe Int)

Decode a CBOR map header, returning the entry count or Nothing for indefinite-length.

Consume a break byte (0xFF).

Use this after decoding a known number of elements from an indefinite-length container.

untilBreak : CborDecoder ctx a -> CborDecoder ctx (List a)

Decode items until a break byte (0xFF) is encountered.

Use this after reading an indefinite-length header with arrayHeader or mapHeader. The element decoder must be Item (i.e. consume bytes).

Escape Hatch

Decode any well-formed CBOR data item into a CborItem.

This is the full CBOR parser. Handles all major types, nested structures, tags, indefinite-length containers, etc.

If you need the raw Bytes for an item, decoding with item then re-encoding with Cbor.Encode.item is fast enough in practice (on par or faster than a dedicated byte-slicing decoder).

Skip any well-formed CBOR data item without allocating anything.

This is the fastest way to move past a CBOR item. Uses BD.skipBytes throughout, staying entirely in the fast lane.

rawBytes : CborDecoder ctx Bytes

Decode any well-formed CBOR data item and return its raw bytes.

Internally this decodes the item with item and re-encodes it with Cbor.Encode.item. There is no way to slice the source bytes directly, which would be faster in general due to allocation costs of small bytes slices.