elm-toulouse / cbor / Cbor.Decode

The Concise Binary Object Representation (CBOR) is a data format whose design goals include the possibility of extremely small code size, fairly small message size, and extensibility without the need for version negotiation. These design goals make it different from earlier binary serializations such as ASN.1 and MessagePack.

Decoder


type Decoder a

Describes how to turn a binary CBOR sequence of bytes into any Elm value.

decode : Decoder a -> Bytes -> Maybe a

Turn a binary CBOR sequence of bytes into a nice Elm value.

maybe : Decoder a -> Decoder (Maybe a)

Helpful for dealing with optional items. Turns null or undefined into Nothing.

D.decode (D.maybe D.bool) Bytes<0xF6> == Just Nothing

D.decode (D.maybe D.bool) Bytes<0xF4> == Just (Just False)

Primitives

bool : Decoder Basics.Bool

Decode a boolean.

D.decode D.bool Bytes<0xF4> == Just False

D.decode D.bool Bytes<0xF5> == Just True

int : Decoder Basics.Int

Decode an integer. Note that there's no granular decoders (nor encoders) because, the CBOR encoding of an integers varies depending on the integer value. Therefore, at the CBOR-level, as in Elm, there's no notion of smaller integers.

D.decode D.int Bytes<0x00> == Just 0

D.decode D.int Bytes<0x00> == Just 0

D.decode D.int Bytes<0x1A, 0x00, 0x02, 0x33, 0x56> == Just 1337

D.decode D.int Bytes<0x2D> == Just -14

bigint : Decoder ( Cbor.Sign, Bytes )

Decode an unbounded integer as a big-endian bytes sequence. This is particularly useful for decoding large integer values which are beyond the max and min safe integer values allowed by Elm.

This can also be used to decode tagged PositiveBigNum and NegativeBigNum integer values following the tag semantic specified in RFC-7049.

-- 0
D.decode D.bigint Bytes<0x00>
    == Just (Positive, Bytes<0x00>)

-- 1337
D.decode D.bigint Bytes<0x19, 0x05, 0x39>
    == Just (Positive, Bytes<0x05, 0x39>)

-- -14
D.decode D.bigint Bytes<0x2D>
    == Just (Negative, Bytes<0x0E>)

-- -1536
D.decode D.bigint Bytes<0x39, 0x05, 0xFF>
    == Just <Negative, Bytes<0x06, 0x00>

-- 2^53
D.decode D.bigint Bytes<0x1B, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>
    == Just (Positive, Bytes<0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>)

-- 2^64
D.decode D.bigint Bytes<0xC2, 0x49, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>
    == Just (Positive, Bytes<0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00>)

float : Decoder Basics.Float

Decode a floating point number, regardless of its precision. Because in Elm, there exists only one 'Float' type, which is encoded in double-precision, we do decode all floats into this double-precision number in Elm which may lead to some precision gain and therefore, different representation than one would expect.

D.decode D.float <| E.encode (E.float16 1.1) == Just 1.099609375

D.decode D.float <| E.encode (E.float64 1.1) == Just 1.1

string : Decoder String

Decode a bunch UTF-8 bytes into a String. In case of streaming, all string chunks are decoded at once and concatenated as if they were one single string. See also Cbor.Encode.beginString.

bytes : Decoder Bytes

Decode a bunch bytes into Bytes. In case of streaming, all byte chunks are decoded at once and concatenated as if they were one single byte string. See also Cbor.Encode.beginBytes.

Data Structures

list : Decoder a -> Decoder (List a)

Decode a List of items a. The list can be definite or indefinite.

D.decode (D.list D.int) Bytes<0x82, 0x0E, 0x18, 0x2A> == Just [ 14, 42 ]

D.decode (D.list D.int) Bytes<0x9F, 0x01, 0x02, 0xFF> == Just [ 1, 1 ]

D.decode (D.list (D.list D.bool)) Bytes<0x81, 0x9F, 0xF4, 0xFF> == Just [ [ False ] ]

length : Decoder Basics.Int

Decode only the length of a (definite) CBOR array.

dict : Decoder comparable -> Decoder a -> Decoder (Dict comparable a)

Decode an CBOR map of pairs of data-items into an Elm Dict. The map can be either of definite length or indefinite length.

D.decode (D.dict D.int D.int) Bytes<0xA1, 0x0E, 0x18, 0x2A>
    == Dict.fromList [ ( 14, 42 ) ]

D.decode (D.dict D.string D.int) Bytes<0xBF, 0x61, 0x61, 0x0E, 0x61, 0x62, 0x18, 0x2A, 0xFF>
    == Dict.fromList [ ( "a", 14 ), ( "b", 42 ) ]

size : Decoder Basics.Int

Decode only the size of a (definite) CBOR map.

associativeList : Decoder k -> Decoder v -> Decoder (List ( k, v ))

Decode a CBOR map as an associative list, where keys and values can be arbitrary CBOR. If keys are comparable, you should prefer dict to this primitive.

fold : Decoder k -> (k -> Decoder (state -> state)) -> state -> Decoder state

Decode a CBOR map by folding over its entries and updating a state variable. Useful to decode record-like structures that have empty values.

type alias Foo =
    { foo : Maybe Int
    , bar : Maybe Bool
    }

decodeFoo : D.Decoder Foo
decodeFoo =
    D.fold D.int
        (\k ->
            case k of
                0 ->
                    D.int |> D.andThen (\v st -> { st | foo = Just v })

                1 ->
                    D.bool |> D.andThen (\v st -> { st | bar = Just v })

                _ ->
                    D.fail
        )
        { foo = Nothing, bar = Nothing }

Records & Tuples


type Step k result

An intermediate (opaque) step in the decoding of a record. See record or tuple for more detail.

record : Decoder k -> steps -> (Step k steps -> Decoder (Step k record)) -> Decoder record

Decode a (definite) CBOR map into a record. Keys in the map can be arbitrary CBOR but are expected to be homogeneous across the record.

type alias Album =
    { artist : String
    , title : String
    , label : Maybe String
    }

-- In this example, we use compact integer as keys.
decodeAlbumCompact : D.Decoder Album
decodeAlbumCompact =
    D.record D.int Album <|
        D.fields
            >> D.field 0 D.string
            >> D.field 1 D.string
            >> D.optionalField 2 D.string

-- In this example, we use more verbose string keys.
decodeAlbumVerbose : D.Decoder Album
decodeAlbumVerbose =
    D.record D.string Album <|
        D.fields
            >> D.field "artist" D.string
            >> D.field "title" D.string
            >> D.optionalField "label" D.string

fields : Step k steps -> Decoder (Step k steps)

A helper that makes writing record decoders nicer. It is equivalent to succeed, but let us align decoders to fight compulsory OCDs.

field : k -> Decoder field -> Decoder (Step k (field -> steps)) -> Decoder (Step k steps)

Decode a field of record and step through the decoder. This ensures that the decoded key matches the expected key. If it doesn't, the entire decoder is failed.

See record for detail about usage.

optionalField : k -> Decoder field -> Decoder (Step k (Maybe field -> steps)) -> Decoder (Step k steps)

Decode an optional field of record and step through the decoder. This ensures that the decoded key matches the expected key. If it doesn't, the entire decoder is failed. When the key is missing, returns Nothing as a value.

See record for detail about usage.

tuple : steps -> (Step Basics.Never steps -> Decoder (Step Basics.Never tuple)) -> Decoder tuple

Decode a (definite) CBOR array into a record / tuple.

type alias Track =
    { title : String
    , duration : Int
    }

decodeTrack : D.Decoder Track
decodeTrack =
    D.tuple Track <|
        D.elems
            >> D.elem D.string
            >> D.elem D.int

elems : Step Basics.Never steps -> Decoder (Step Basics.Never steps)

A helper that makes writing record decoders nicer. It is equivalent to succeed, but let us align decoders to fight compulsory OCDs.

elem : Decoder field -> Decoder (Step Basics.Never (field -> steps)) -> Decoder (Step Basics.Never steps)

Decode an element of a record or tuple and step through the decoder.

See tuple for detail about usage.

optionalElem : Decoder field -> Decoder (Step Basics.Never (Maybe field -> steps)) -> Decoder (Step Basics.Never steps)

Decode an optional element of a record or tuple and step through the decoder. Note that optional elements only make sense at the end of a structure.

In particular, optional elements must be contiguous and can only be optional (resp. missing) if all their successors are also optional (resp. missing).

For example, consider:

type alias Foo =
    Foo
        { a : Int
        , b : Maybe Int
        , c : Maybe Int
        }

decodeFoo =
    tuple Foo <|
        elems
            >> elem int
            >> optionalElem int
            >> optionalElem int

If you need such behavior, use elem with maybe.

Mapping

succeed : a -> Decoder a

Always succeed to produce a certain Elm value.

D.decode (D.succeed 14) Bytes<0x42, 0x28> == Just 14

D.decode (D.list (D.int |> D.andThen (\_ -> D.succeed 1))) Bytes<0x83, 0x00, 0x00, 0x00>
    == Just [ 1, 1, 1 ]

This particularly handy when used in combination with andThen.

fail : Decoder a

A decoder that always fail.

D.decode D.fail Bytes<0x00> == Nothing

This is particularly handy when used in combination with andThen

andThen : (a -> Decoder b) -> Decoder a -> Decoder b

Decode something and then use that information to decode something else. This is useful when a 'Decoder' depends on a value held by another decoder:

tagged : Tag -> Decoder a -> Decoder a
tagged expectedTag decodeA =
    tag
        |> andThen
            (\decodedTag ->
                if decodedTag == expectedTag then
                    decodeA

                else
                    fail
            )

ignoreThen : Decoder a -> Decoder ignored -> Decoder a

Decode something and then another thing, but only continue with the second result.

ignoreThen a ignored =
    ignored |> andThen (always a)

thenIgnore : Decoder ignored -> Decoder a -> Decoder a

Decode something and then another thing, but only continue with the first result.

thenIgnore ignored a =
    a |> andThen (\result -> map (always result) ignored)

map : (a -> value) -> Decoder a -> Decoder value

Transform a decoder. For example, maybe you just want to know the length of a string:

import String

stringLength : Decoder Int
stringLength =
    D.map String.length D.string

map2 : (a -> b -> value) -> Decoder a -> Decoder b -> Decoder value

Try two decoders and then combine the result. Can be used to decode objects with many fields:

type alias Point =
    { x : Float, y : Float }

point : Decoder Point
point =
    D.map2 Point D.float D.float

It tries each individual decoder and puts the result together with the Point constructor.

map3 : (a -> b -> c -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder value

Try three decoders and then combine the result. Can be used to decode objects with many fields:

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

person : Decoder Person
person =
    D.map3 Person D.string D.int D.float

Like map2 it tries each decoder in order and then give the results to the Person constructor. That can be any function though!

map4 : (a -> b -> c -> d -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder value

Try four decoders and then combine the result. See also 'map3' for some examples.

map5 : (a -> b -> c -> d -> e -> value) -> Decoder a -> Decoder b -> Decoder c -> Decoder d -> Decoder e -> Decoder value

Try five decoders and then combine the result. See also 'map3' for some examples.

traverse : (a -> Decoder b) -> List a -> Decoder (List b)

Apply a decoder to all elements of a List, in order, and yield the resulting resulting List.

Branching

oneOf : List (Decoder a) -> Decoder a

Decode something that can have one of many shapes without prior information on the correct shape.

type IntOrString
    = IntVariant Int
    | StringVariant String

intOrString : Decoder IntOrString
intOrString =
    D.oneOf [ D.map IntVariant D.int, D.map StringVariant D.string ]

Bytes<0x64, 0xF0, 0x9F, 0x8C, 0x88>
    |> D.decode intOrString
--> (Just <| StringVariant "🌈")

keep : Decoder a -> Decoder (a -> b) -> Decoder b

Decode the next value from a structure, and continue decoding.

This is particularly useful when manually decoding an array-like or map-like structure that materialize multi-variant constructors. For example, imagine the following:

type Hostname
    = Single Ipv4 TcpPort
    | Multi AaaRecord TcpPort
    | Dns SrvRecord

Which could be encoded as such:

toCBOR : Hostname -> E.Encoder
toCBOR host =
    case host of
        Single ip tcpPort ->
            E.sequence
                [ E.length 3
                , E.int 0
                , Ipv4.toCbor ip
                , TcpPort.toCbor tcpPort
                ]

        Multi record tcpPort ->
            E.sequence
                [ E.length 3
                , E.int 1
                , AaaRecord.toCbor record
                , TcpPort.toCbor tcpPort
                ]

        Dns record ->
            E.sequence
                [ E.length 2
                , E.int 2
                , SrvRecord.toCbor record
                ]

Here, the usual tuple helper would be hopeless since we can't commit to a single constructor a priori. Using oneOf is also unsatisfactory as we could branch into the right decoder by just peaking at the first byte. Using keep and ignore we can write an efficient decoder as such:

fromCbor : D.Decoder Hostname
fromCbor =
    D.length
        |> D.andThen (\len -> D.map (\tag -> ( len, tag )) D.int)
        |> D.andThen
            (\( _, tag ) ->
                case tag of
                    0 ->
                        succeed Single
                            |> keep Ipv4.fromCbor
                            |> keep TcpPort.fromCbor

                    1 ->
                        succeed Multi
                            |> keep AaaRecord.fromCbor
                            |> keep TcpPort.fromCbor

                    2 ->
                        succeed Dns
                            |> SrvRecord.fromCbor

                    _ ->
                        D.fail
            )

ignore : Decoder ignore -> Decoder keep -> Decoder keep

Ignore a field when manually decoding a structure. See keep for details

Streaming

beginString : Decoder ()

Decode the beginning of an indefinite String sequence.

This is useful in combination with ignoreThen and andThen to manually decode sequences of text strings.

beginBytes : Decoder ()

Decode the beginning of an indefinite Bytes sequence.

This is useful in combination with ignoreThen and andThen to manually decode sequences of byte strings.

beginList : Decoder ()

Decode the beginning of an indefinite List.

This is useful in combination with ignoreThen and andThen to manually decode indefinite List.

beginDict : Decoder ()

Decode the beginning of an indefinite Dict.

This is useful in combination with ignoreThen and andThen to manually decode indefinite Dict.

break : Decoder ()

Decode a termination of an indefinite structure. See beginString, beginBytes, beginList, beginDict for detail about usage.

Tagging

tag : Decoder Cbor.Tag.Tag

This implementation does little interpretation of the tags and is limited to only decoding the tag's value. The tag's payload has to be specified as any other decoder. So, using the previous example, one could decode a bignum as:

D.decode (D.tag |> D.andThen (\tag -> D.bytes)) input

You may also use tagged if you as a helper to decode a tagged value, while verifying that the tag matches what you expect.

D.decode (D.tagged PositiveBigNum D.bytes) input

tagged : Cbor.Tag.Tag -> Decoder a -> Decoder ( Cbor.Tag.Tag, a )

Decode a value that is tagged with the given Tag. Fails if the value is not tagged, or, tagged with some other Tag

D.decode (D.tagged Cbor D.int) Bytes<0xD8, 0x0E> == Just ( Cbor, 14 )

D.decode (D.tagged Base64 D.int) Bytes<0xD8, 0x0E> == Nothing

D.decode (D.tagged Cbor D.int) Bytes<0x0E> == Nothing

Debugging

any : Decoder CborItem

Decode remaining bytes as any CborItem. This is useful for debugging or to inspect some unknown Cbor data.

D.decode D.any <| E.encode (E.int 14) == Just (CborInt 14)

raw : Decoder Bytes

Decode the next cbor item as a raw sequence of Bytes. Note that this primitive is innefficient since it needs to parse the underlying CBOR first, and re-encode it into a sequence afterwards. Use for debugging purpose only.