elm-toulouse/cbor/1.1.0

NOTE: the latest version is 4.0.1

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 a nice Elm value.

decode : Decoder a -> Bytes -> Maybe a

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

import Cbor.Decode as D
import Url exposing (Url)


type alias Album =
    { artist : String
    , title : String
    , year : Int
    , tracks : List ( String, Duration )
    , links : List Url
    }


type Duration
    = Duration Int


decodeAlbum : D.Decoder Album
decodeAlbum =
    let
        link =
            D.string
                |> D.map Url.fromString
                |> D.andThen (Maybe.map D.succeed >> Maybe.withDefault D.fail)


        track =
            D.pair D.string (D.map Duration D.int)
    in
    D.map5 Album
        D.string
        D.string
        D.int
        (D.list track)
        (D.list link)

Primitives

bool : Decoder Bool

Decode a boolean.

int : Decoder Int

Decode an integer. Note that there's no granular decoders (nor encoders) for integers like 'unsignedInt8' or 'signedInt32' 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.

float : Decoder 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


E.Encode (E.float16 1.1) == Bytes<0xF9, 0x3C, 0x66>


E.Encode (E.float64 1.1) == Bytes<0xFB, 0x3F, 0xF1, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A>
string : Decoder String

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

bytes : Decoder Bytes

Decode a bunch bytes into 'Bytes'. In case of streaming, all bytes are decoded at once and concatenated as if they were one big 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 finite or infinite (see also Streaming in Cbor.Encode).

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 ] ]

Decode an array or said differently, a list with heterogeneous elements (with different types).

Since Elm (or statically typed languages in general) does not support hetegorenous arrays, we have to resort to records when parsing. This parser works best with the map family of functions.

For example, to decode an array with 3 elements:

D.decode (D.array <| D.map3 MyRecord D.bool D.int D.string) (Bytes<0x83 0xF5 0x0E 0x61 0x61>)
    == Just (MyRecord True 14 "a")
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 finite length or indefinite length (see also Streaming in Cbor.Encode).

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 ) ]
keyValueMap : Decoder k -> Decoder v -> Decoder (List ( k, v ))

Decode a CBOR (key, value) map, where keys can be potentially arbitrary CBOR.

If keys are comparable, you should prefer dict to this primitive.

Like 'array', but for heterogeneous dict.

pair : Decoder a -> Decoder b -> Decoder ( a, b )

Decode a 2-tuple. This is mostly a helper around a list decoder with 2 elements, with non-uniform types.

D.decode (D.pair D.int D.bool) Bytes<0x0E, 0xF5> == Just ( 14, True )
maybe : Decoder a -> Decoder (Maybe a)

Helpful for dealing with optional fields. Turns null into Nothing.

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


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

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.

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
            )
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.

(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!

(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.

(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.

Tagging

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 : Tag -> Decoder a -> Decoder ( Tag, a )

Decode a value that is tagged with the given 'Tag'. Fails if the value is not tagged, or, tag 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

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 (CborUnsignedInteger 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.