Library for turning Protobuf messages into Elm values.
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:
proto2
only);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
}
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") []
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 }
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 Int64
Decode a variable number of bytes into an integer from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807.
uint64 : Decoder Int64
Decode a variable number of bytes into an integer from 0 to 18,446,744,073,709,551,615
sint64 : Decoder Int64
Decode a variable number of bytes into an integer from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
fixed64 : Decoder Int64
Decode eight bytes into an integer from 0 to 9,223,372,036,854,775,807.
sfixed64 : Decoder Int64
Decode eight bytes into an integer from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,808.
double : Decoder Basics.Float
Decode eight bytes into a floating point number.
float : Decoder Basics.Float
Decode four bytes into a floating point number.
string : Decoder String
Decode all bytes into a string.
bool : Decoder Basics.Bool
Decode one byte into a boolean.
bytes : Decoder Bytes
Copy all bytes into a new Bytes
sequence.
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 : (() -> Decoder a) -> Decoder a
Sometimes you have messages with a recursive structure, like nested
comments. You must use lazy
to 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.