miniBill / elm-bare / Codec.Bare

This module is an implementation of the BARE format, which at the time of writing is an IETF draft.

The names of the functions correspond, as far as possible, to the ones in the original specification.

Most of the following documentation is taken from the original specification (CC-BY-SA), and while the code is licensed with a MIT license, this documentation is thus CC-BY-SA itself.

Binary Application Record Encoding (BARE) is, as the name implies, a simple binary representation for structured application data.

BARE messages omit type information, and are not self-describing. The structure of a message must be established out of band, generally by prior agreement and context - for example, if a BARE message is returned from /api/user/info, it can be inferred from context that the message represents user information, and the structure of such messages is available in the documentation for this API.

Types


type Codec a

A value that knows how to encode and decode a sequence of bytes.

A Codec a contains a Bytes.Decoder a and the corresponding a -> Bytes.Encoder.


type alias Encoder =
Bytes.Encode.Encoder

Describes how to generate a sequence of bytes.


type alias Bytes =
Bytes

A sequence of bytes. Refer to the elm/bytes docs for more information.

Decode


type alias Decoder a =
Bytes.Decode.Decoder a

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

decoder : Codec a -> Decoder a

Extracts the Decoder contained inside the Codec.

decodeValue : Codec a -> Bytes -> Maybe a

Run a Codec to turn a sequence of bytes into an Elm value.

Encode

encoder : Codec a -> a -> Encoder

Extracts the encoding function contained inside the Codec.

encodeToValue : Codec a -> a -> Bytes

Convert an Elm value into a sequence of bytes.

Primitive Types

Integers

uint : Codec Basics.Int

An unsigned integer with a variable-length encoding. Each octet of the encoded value has the most-significant bit set, except for the last octet. The remaining bits are the integer value in 7-bit groups, least-significant first.

The maximum precision of such a number is 56-bits (64-bits in the original spec, but Elm Ints start to become unreliable after 56-bits, so caveat emptor). The maximum length of an encoded uint should thus be 8 octects but can be up to 10 octects for out-of-range integers.

int : Codec Basics.Int

A signed integer with a variable-length encoding. Signed integers are represented as uint using a "zig-zag" encoding: positive values x are written as 2x + 0, negative values are written as 2(^x) + 1. In other words, negative numbers are complemented and whether to complement is encoded in bit 0

The maximum precision of such a number is 56-bits (64-bits in the original spec, but Elm Ints start to become unreliable after 56-bits, so caveat emptor). The maximum length of an encoded uint should thus be 8 octects but can be up to 10 octects for out-of-range integers.

Unsigned fixed precision integers

Unsigned integers of a fixed precision, respectively 8, 16, 32, and 64 bits. They are encoded in little-endian (least significant octet first).

u8 : Codec Basics.Int

u16 : Codec Basics.Int

u32 : Codec Basics.Int

u64 : Codec Basics.Int

WARNING (from the official Elm docs): Note: Int math is well-defined in the range -2^31 to 2^31 - 1. Outside of that range, the behavior is determined by the compilation target. When generating JavaScript, the safe range expands to -2^53 to 2^53 - 1 for some operations, but if we generate WebAssembly some day, we would do the traditional integer overflow. This quirk is necessary to get good performance on quirky compilation targets.

Signed fixed precision integers

Signed integers of a fixed precision, respectively 8, 16, and 32 bits. elm-bare does not support signed 64 bits integers yet (PRs welcome). They are encoded in little-endian (least significant octet first), with two's compliment notation.

i8 : Codec Basics.Int

i16 : Codec Basics.Int

i32 : Codec Basics.Int

Floating point numbers

Floating-point numbers represented with the IEEE 754 binary32 and binary64 floating point number formats.

f32 : Codec Basics.Float

Due to ElmFloat\s being 64-bit, encoding and decoding it as a 32-bit float won't be exactly equal to the original value.

f64 : Codec Basics.Float

Others

bool : Codec Basics.Bool

A boolean value, either true or false, encoded as a u8 type with a value of one or zero, respectively representing true or false.

If a value other than one or zero is found in the u8 representation of the bool, the message is considered invalid.

enum : List a -> Codec a

An unsigned integer value from a set of possible values agreed upon in advance, encoded with the uint type.

An enum whose uint value is not a member of the values agreed upon in advance is considered invalid.

Note that this makes the enum type unsuitable for representing several enum values which have been combined with a bitwise OR operation.

enumWithValues : List ( a, Basics.Int ) -> Codec a

An unsigned integer value from a set of possible values agreed upon in advance, encoded with the uint type.

This version allows you to specify how values will be encoded.

An enum whose uint value is not a member of the values agreed upon in advance is considered invalid.

Note that this makes the enum type unsuitable for representing several enum values which have been combined with a bitwise OR operation.

string : Codec String

A string of text. The length of the text in octets is encoded first as a uint, followed by the text data represented with the UTF-8 encoding.

If the data is found to contain invalid UTF-8 sequences, it is considered invalid.

char : Codec Char

A single Char. It is encoded as a string.

dataWithLength : Basics.Int -> Codec Bytes

Arbitrary data with a fixed "length" in octets, e.g. dataWithLength 16. The data is encoded literally in the message.

data : Codec Bytes

Arbitrary data of a variable length in octets. The length is encoded first as a uint, followed by the data itself encoded literally.

void : Codec ()

A type with zero length. It is not encoded into BARE messages.

Aggregate types

optional : Codec a -> Codec (Maybe a)

A value which may or may not be present. Represented as either a u8 with a value of zero, indicating that the optional value is unset; or a u8 with a value of one, followed by the encoded data of the optional type.

An optional value whose initial u8 is set to a number other than zero or one is considered invalid.

listWithLength : Basics.Int -> Codec a -> Codec (List a)

A list of values of an uniform type, with a fixed length. The length is not encoded into the message. The encoded values of each member of the list are concatenated to form the encoded list.

list : Codec a -> Codec (List a)

A variable-length list of values of an uniform type. The length of the list (in values) is encoded as a uint, followed by the encoded values of each member of the list concatenated.

arrayWithLength : Basics.Int -> Codec a -> Codec (Array a)

An array of values of an uniform type, with a fixed length. The length is not encoded into the message. The encoded values of each member of the array are concatenated to form the encoded array.

array : Codec a -> Codec (Array a)

A variable-length array of values of an uniform type. The length of the array (in values) is encoded as a uint, followed by the encoded values of each member of the array concatenated.

set : Codec comparable -> Codec (Set comparable)

A variable-length set of values of an uniform type. The length of the set (in values) is encoded as a uint, followed by the encoded values of each member of the set concatenated.

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

An associative list of values of type a keyed by values of type comparable, e.g. dict u32 string. The encoded representation of a map begins with the number of key/value pairs as a uint, followed by the encoded key/value pairs concatenated. Each key/value pair is encoded as the encoded key concatenated with the encoded value.

This is not called map to avoid clashing with the common usage of the term in Elm.

Tagged unions


type TaggedUnionCodec match v

A partially built Codec for a tagged union (called a custom type in Elm).

taggedUnion : match -> TaggedUnionCodec match value

Starts building a Codec for a tagged union.

A tagged union is a value that can be one of any type from a set. Each type in the set is assigned a numeric representation, starting at zero and incrementing for each type. The value is encoded as the selected tag as a uint, followed by the value itself encoded as that type.

elm-bare allows you to map this directly to Elm custom types. For easier interoperability with other languages you should restrict yourself to variant0 and variant1 (using struct to simulate multiple arguments).

You need to pass a pattern matching function, see the FAQ for details.

type Semaphore
    = Red Int String Bool
    | Yellow Float
    | Green

semaphoreCodec : Codec Semaphore
semaphoreCodec =
    Codec.taggedUnion
        (\redEncoder yellowEncoder greenEncoder value ->
            case value of
                Red i s b ->
                    redEncoder i s b

                Yellow f ->
                    yellowEncoder f

                Green ->
                    greenEncoder
        )
        |> Codec.variant3 0 Red Codec.signedInt Codec.string Codec.bool
        |> Codec.variant1 1 Yellow Codec.float64
        |> Codec.variant0 2 Green
        |> Codec.buildTaggedUnion

variant0 : Basics.Int -> v -> TaggedUnionCodec (Encoder -> a) v -> TaggedUnionCodec a v

Define a variant with 0 parameters for a tagged union.

variant1 : Basics.Int -> (a -> v) -> Codec a -> TaggedUnionCodec ((a -> Encoder) -> b) v -> TaggedUnionCodec b v

Define a variant with 1 parameters for a tagged union.

variant2 : Basics.Int -> (a -> b -> v) -> Codec a -> Codec b -> TaggedUnionCodec ((a -> b -> Encoder) -> c) v -> TaggedUnionCodec c v

Define a variant with 2 parameters for a tagged union.

variant3 : Basics.Int -> (a -> b -> c -> v) -> Codec a -> Codec b -> Codec c -> TaggedUnionCodec ((a -> b -> c -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 3 parameters for a tagged union.

variant4 : Basics.Int -> (a -> b -> c -> d -> v) -> Codec a -> Codec b -> Codec c -> Codec d -> TaggedUnionCodec ((a -> b -> c -> d -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 4 parameters for a tagged union.

variant5 : Basics.Int -> (a -> b -> c -> d -> e -> v) -> Codec a -> Codec b -> Codec c -> Codec d -> Codec e -> TaggedUnionCodec ((a -> b -> c -> d -> e -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 5 parameters for a tagged union.

variant6 : Basics.Int -> (a -> b -> c -> d -> e -> f -> v) -> Codec a -> Codec b -> Codec c -> Codec d -> Codec e -> Codec f -> TaggedUnionCodec ((a -> b -> c -> d -> e -> f -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 6 parameters for a tagged union.

variant7 : Basics.Int -> (a -> b -> c -> d -> e -> f -> g -> v) -> Codec a -> Codec b -> Codec c -> Codec d -> Codec e -> Codec f -> Codec g -> TaggedUnionCodec ((a -> b -> c -> d -> e -> f -> g -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 7 parameters for a tagged union.

variant8 : Basics.Int -> (a -> b -> c -> d -> e -> f -> g -> h -> v) -> Codec a -> Codec b -> Codec c -> Codec d -> Codec e -> Codec f -> Codec g -> Codec h -> TaggedUnionCodec ((a -> b -> c -> d -> e -> f -> g -> h -> Encoder) -> partial) v -> TaggedUnionCodec partial v

Define a variant with 8 parameters for a tagged union.

buildTaggedUnion : TaggedUnionCodec (a -> Encoder) a -> Codec a

Build a Codec for a fully specified tagged union.

Structs


type StructCodec a b

A partially built Codec for a struct (called an object in Elm).

struct : b -> StructCodec a b

Start creating a Codec for a struct. You should pass the main constructor as argument.

A struct is a set of values of arbitrary types, concatenated together in an order known in advance.

If you don't have one (for example it's a simple type with no name), you should pass a function that given the field values builds an object.

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

pointCodec : Codec Point
pointCodec =
    Codec.struct Point
        |> Codec.field .x Codec.signedInt
        |> Codec.field .y Codec.signedInt
        |> Codec.buildStruct

field : (a -> f) -> Codec f -> StructCodec a (f -> b) -> StructCodec a b

Specify how to get a value from the struct we want to encode and then give a Codec for that value.

buildStruct : StructCodec a a -> Codec a

Create a Codec from a fully specified StructCodec.

Mapping

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

Transform a Codec.

andThen : (a -> Maybe b) -> (b -> a) -> Codec a -> Codec b

Transform a Codec in a way that can potentially fail when decoding.

{-| Volume must be between 0 and 1.
-}
volumeCodec =
    Codec.float64
        |> Codec.andThen
            (\volume ->
                if volume <= 1 && volume >= 0 then
                    Just volume

                else
                    Nothing
            )
            (\volume -> volume)

Note that this function is a bit risky. If you encode data that fails to decode, you won't get any indication as to what happened.

Fancy Codecs

lazy : (() -> Codec a) -> Codec a

This is useful for recursive structures that are not easily modeled with recursive.

type Peano
    = Peano (Maybe Peano)

{-| This is the same example used in Codec.recursive but adapted for lazy.
-}
peanoCodec : Codec Peano
peanoCodec =
    Codec.maybe (Codec.lazy (\() -> peanoCodec)) |> Codec.map Peano (\(Peano a) -> a)

recursive : (Codec a -> Codec a) -> Codec a

Create a Codec for a recursive data structure. The argument to the function you need to pass is the fully formed Codec, see the FAQ for details.

type Peano
    = Peano (Maybe Peano)

peanoCodec : Codec Peano
peanoCodec =
    Codec.recursive
        (\finishedCodec ->
            Codec.maybe finishedCodec
                |> Codec.map Peano (\(Peano p) -> p)
        )