eriktim / elm-protocol-buffers / Protobuf.Decode

Library for turning Protobuf messages into Elm values.

Decoding


type Decoder a

Describes how to turn a sequence of Protobuf-encoded bytes into a nice Elm value.

import Protobuf.Decode as Decode

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

personDecoder : Decode.Decoder Person
personDecoder =
    Decode.message (Person 0 "")
        |> Decode.optional 1 Decode.int32 setAge
        |> Decode.optional 2 Decode.string setName

-- SETTERS
setAge : a -> { b | age : a } -> { b | age : a }
setAge value model =
    { model | age = value }

setName : a -> { b | name : a } -> { b | name : a }
setName value model =
    { model | name = value }

decode : Decoder a -> Bytes -> Maybe a

Turn a sequence of bytes into a nice Elm value.

 decode int32 <7F>    -- Just 127
 decode sint32 <7F>   -- Just -64
 decode sfixed32 <7F> -- Nothing

The Decoder specifies exactly how this should happen. This process may fail if:

The examples above show a case where there are not enough bytes. They also show the same bytes sequence can lead to different values depending on the Decoder that is being used. Decoders cannot always detect these kind of mismatches.

Values are always encoded together with a field number and their wire type . This allows the decoder to set the right fields and to process the correct number of bytes.

expectBytes : (Result Http.Error a -> msg) -> Decoder a -> Http.Expect msg

Turn a Decoder into a Http.Expect. You probably received the Bytes you want to decode from an HTTP request. As message consumes all remaining bytes on the wire, you cannot use Http.expectBytes directly (as it is not aware of the width of the bytes sequence). Hence, you might want to use the expectBytes as provided by this package.

import Http
import Protobuf.Decode as Decode

getPerson : (Result Http.Error a -> msg) -> Cmd msg
getPerson toMsg =
    Http.get
      { url = "https://example.com/person"
      , Decode.expectBytes toMsg personDecoder
      }


type FieldDecoder a

Describes how to decode a certain field in a Protobuf-encoded message and how to update a record with the new Elm value.

message : a -> List (FieldDecoder a) -> Decoder a

Decode all remaining bytes into an record. The initial value given here holds all default values (which cannot be overridden for proto3). Each provided field decoder calls a setter function to update the record when its field number is encountered on the bytes sequence. Unknown fields that have no matching field decoder are currently being ignored.

import Protobuf.Decode as Decode

type alias Person =
    { name : String
    }

personDecoder : Decode.Decoder Person
personDecoder =
    -- Person "John"
    Decode.message (Person "John") []

Field Decoders

required : Basics.Int -> Decoder a -> (a -> b -> b) -> FieldDecoder b

Decode a required field. Decoding a message fails when one of its required fields is not present in the bytes sequence. Required fields are only supported in proto2.

type alias Person =
    { age : Int -- field number 1
    , name : String -- field number 3
    }

personDecoder : Decode.Decoder Person
personDecoder =
    -- <08 21 1A 04 4A 6F 68 6E> == Just (Person 33 "John")
    -- <08 21>                   == Nothing
    -- <>                        == Nothing
    Decode.message (Person 0 "")
        [ Decode.required 1 int32 setAge
        , Decode.required 3 string setName
        ]

-- SETTERS
setAge : a -> { b | age : a } -> { b | age : a }
setAge value model =
    { model | age = value }

setName : a -> { b | name : a } -> { b | name : a }
setName value model =
    { model | name = value }

optional : Basics.Int -> Decoder a -> (a -> b -> b) -> FieldDecoder b

Decode an optional field.

import Protobuf.Decode as Decode

type alias Person =
    { age : Int -- field number 2
    , name : String -- field number 4
    }

personDecoder : Decode.Decoder Person
personDecoder =
    -- <08 21 1A 04 4A 6F 68 6E> == Just (Person 33 "John")
    -- <08 21>                   == Just (Person 33 "")
    -- <>                        == Just (Person 0 "")
    Decode.message (Person 0 "")
        [ Decode.optional 2 int32 setAge
        , Decode.optional 4 string setName
        ]

-- SETTERS
setAge : a -> { b | age : a } -> { b | age : a }
setAge value model =
    { model | age = value }

setName : a -> { b | name : a } -> { b | name : a }
setName value model =
    { model | name = value }

repeated : Basics.Int -> Decoder a -> (b -> List a) -> (List a -> b -> b) -> FieldDecoder b

Decode a repeated field. If no such fields are present when decoding a message, the result will be an empty list.

As repeated fields may occur multiple times in a bytes sequence, repeated also needs to get hold of the record's current value in order to append the new value.

import Protobuf.Decode as Decode

type alias Person =
    { names : List String -- field number 5
    }

personDecoder : Decode.Decoder Person
personDecoder =
    -- <2A 04 4A 6F 68 6E 2A 07 4D 61 72 77 6F 6F 64> == Just (Person [ "John", "Marwood" ])
    -- <2A 04 4A 6F 68 6E>                            == Just (Person [ "John" ])
    -- <>                                             == Just (Person [])
    Decode.message (Person [])
        [ Decode.repeated 5 string .names setNames
        ]

-- SETTERS
setNames : a -> { b | names : a } -> { b | names : a }
setNames value model =
    { model | names = value }

mapped : Basics.Int -> ( comparable, a ) -> Decoder comparable -> Decoder a -> (b -> Dict comparable a) -> (Dict comparable a -> b -> b) -> FieldDecoder b

Decode a map field. If no such fields are present when decoding a message, the result will be an empty Dict. Note that you need to provide one decoder for the keys and another one for the values. Keys without a value or values without a key stick to the provided defaults.

As map fields may occur multiple times in a bytes sequence, mapped also needs to get hold of the record's current value in order to append the new value.

import Dict exposing (Dict)
import Protobuf.Decode as Decode

type alias Administration =
    { persons : Dict Int String -- field number 6
    }

administrationDecoder : Decode.Decoder Administration
administrationDecoder =
    -- <32 08 08 01 12 04 4A 6F 68 6E 32 08 08 02 12 04 4B 61 74 65> == Just (Administration (Dict.fromList [( 1, "John" ), ( 2, "Kate" )])
    -- <32 08 08 01 12 04 4A 6F 68 6E>                               == Just (Administration (Dict.fromList [( 1, "John" )])
    -- <32 08 08 01>                                                 == Just (Administration (Dict.fromList [( 1, "" )])
    -- <>                                                            == Just (Administration Dict.empty)
    Decode.message (Administration Dict.empty)
        [ Decode.mapped 6 ( 0, "" ) int32 string .persons setPersons
        ]

-- SETTERS
setPersons : a -> { b | persons : a } -> { b | persons : a }
setPersons value model =
    { model | persons = value }

oneOf : List ( Basics.Int, Decoder a ) -> (Maybe a -> b -> b) -> FieldDecoder b

Decode one of some fields. As the decoder is capable of deserializing different types of data its return type must be a custom type.

import Protobuf.Decode as Decode

type alias FormValue =
    { key : String -- field number 7
    , value : Maybe Value -- field number 8 or 9
    }

type Value
    = StringValue String
    | IntValue Int

formValueDecoder : Decode.Decoder FormValue
formValueDecoder =
    -- <0A 03 6B 65 79 12 05 76 61 6C 75 65> == Just (FormValue "key" (StringValue "value"))
    -- <0A 03 6B 65 79 10 64>                == Just (FormValue "key" (IntValue 100))
    -- <0A 03 6B 65 79>                      == Just (FormValue "key" NoValue)
    -- <>                                    == Just (FormValue "" NoValue)
    Decode.message (FormValue "" NoValue)
        [ Decode.optional 7 string setKey
        , Decode.oneOf
            [ ( 8, Decode.map StringValue Decode.string )
            , ( 9, Decode.map IntValue Decode.int32 )
            ]
            setValue
        ]

-- SETTERS
setKey : a -> { b | key : a } -> { b | key : a }
setKey value model =
    { model | key = value }

setValue : a -> { b | value : a } -> { b | value : a }
setValue value model =
    { model | value = value }

Integers

int32 : Decoder Basics.Int

Decode a variable number of bytes into an integer from -2147483648 to 2147483647.

uint32 : Decoder Basics.Int

Decode a variable number of bytes into an integer from 0 to 4294967295.

sint32 : Decoder Basics.Int

Decode a variable number of bytes into an integer from -2147483648 to 2147483647.

fixed32 : Decoder Basics.Int

Decode four bytes into an integer from 0 to 4294967295.

sfixed32 : Decoder Basics.Int

Decode four bytes into an integer from -2147483648 to 2147483647.

int64 : Decoder Protobuf.Types.Int64.Int64

Decode a variable number of bytes into an integer from -9223372036854775808 to 9223372036854775807.

uint64 : Decoder Protobuf.Types.Int64.Int64

Decode a variable number of bytes into an integer from 0 to 18446744073709551615.

sint64 : Decoder Protobuf.Types.Int64.Int64

Decode a variable number of bytes into an integer from -9223372036854775808 to 9223372036854775807.

fixed64 : Decoder Protobuf.Types.Int64.Int64

Decode a eight bytes into an integer from 0 to 18446744073709551615.

sfixed64 : Decoder Protobuf.Types.Int64.Int64

Decode eight bytes into an integer from -9223372036854775808 to 9223372036854775807.

Floats

double : Decoder Basics.Float

Decode eight bytes into a floating point number.

float : Decoder Basics.Float

Decode four bytes into a floating point number.

Strings

string : Decoder String

Decode all bytes into a string.

Booleans

bool : Decoder Basics.Bool

Decode one byte into a boolean.

Bytes

bytes : Decoder Bytes

Copy all bytes into a new Bytes sequence.

Map

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

Transform the value produced by a decoder. This is useful when encoding custom types as an enumeration:

type Fruit
    = Apple
    | Banana
    | Mango
    | Unrecognized Int

fruitDecoder : Decoder Fruit
fruitDecoder =
    Decode.int32
        |> Decode.map
            (\value ->
                case value of
                    0 ->
                        Apple

                    1 ->
                        Banana

                    2 ->
                        Mango

                    v ->
                        Unrecognized v
            )

Unrecognized Int is only used for values that are present but not known. For proto2 decoding it is left out and unrecognized values are left out.

Lazy

lazy : (() -> Decoder a) -> Decoder a

Sometimes you have messages with a recursive structure, like nested comments. You must use lazyto make sure your decoder unrolls lazily.

type alias Comment =
    { message : String
    , responses : Responses
    }

type Responses
    = Responses (List Comment)

commentDecoder : Decoder Comment
commentDecoder =
    Decode.message (Comment "" (Responses []))
        [ Decode.optional 1 Decode.string setMessage
        , Decode.repeated 2
            (Decode.lazy (\_ -> commentDecoder))
            (unwrapResponses << .responses)
            (setResponses << Responses)
        ]

-- SETTERS
setMessage : a -> { b | message : a } -> { b | message : a }
setMessage value model =
    { model | message = value }

setResponses : a -> { b | responses : a } -> { b | responses : a }
setResponses value model =
    { model | responses = value }

unwrapResponses : Responses -> List Comment
unwrapResponses (Responses responses) =
    responses

Here you can read more about recursive data structures.