Codex (bin_codex v0.1.6) View Source

Provides functions to create composable and bidirectional serializers.

Installation

def deps do
  [
    {:bin_codex, "~> 0.1.0"}
  ]
end

Usage

The Codex module provides a number of predefined codecs and combinators that you can use to build new codes.

iex> first_codec = sequence([byte(), byte(), byte()])
...> <<0x10, 0xFF, 0xAB>> |> decode(first_codec)
{:ok, [16, 255, 171], <<>>}
...> [16, 255, 171] |> encode(first_codec)
{:ok, <<0x10, 0xFF, 0xAB>>}

Inspiration

This library draws inspiration from https://github.com/scodec/scodec

Link to this section Summary

Functions

Appends a list codec with a codec to form a new list.

Encodes/decodes a single bit as either 0 or 1.

Encodes/decodes a series of bits as an unsigned integer.

Encodes/decodes a single bit as either true (if 1) or false (if 0).

Encodes/decodes a single byte as an unsigned integer.

Encodes/decodes a series of bytes as an unsigned integer.

Tries to encode/decode each codec in succession, using the first one that succeeds.

Combines two codecs into a codec that produces a 2-element tuple of each value.

Combines a codec with a list codec to form a new list.

A codec that always encodes to and decodes from the same value.

Used to convert a codec to another type.

Creates a new codec.

Decodes a value from its binary form using the supplied codec.

Only succeeds if there were no bits left to parse.

A codec that always decodes to an empty list, [].

Encodes a value to its binary form using the supplied codec.

Fails a codec if the result doesn't satisfy the predicate.

Creates a codec that always fails with the supplied error message.

Creates a codec that always fails with the supplied error messages.

Uses the first codec, falling back to the second if it fails.

Decodes a list of codec of length count.

Tests whether a codec would succeed. Decodes to true if it would, false otherwise.

Inverts a boolean codec.

A codec that always decodes to nil.

Creates a codec that might not work. If it fails decoding it returns nil instead.

Pads the bitstring before decoding.

Creates a codec that decodes without actually consuming the bits.

Tests whether a codec would succeed. Decodes to true if it would, false otherwise.

Fails a codec if the result satisfies the predicate.

Reverses a list codec.

Combines a list of codecs into a single codec that produces a list of those values.

Use the result of a codec to create the next codec.

A codec that always decodes to a certain value.

Link to this section Types

Specs

boolean_codec() :: codec(boolean())

Specs

codec(a) :: %Codex{decode: decoder(a), encode: encoder(a)}

Specs

decode_result(a) ::
  {:ok, a, remaining_bits()} | {:error, String.t(), remaining_bits()}

Specs

decoder(a) :: (bitstring() -> decode_result(a))

Specs

encode_result() :: {:ok, bitstring()} | {:error, String.t()}

Specs

encoder(a) :: (a -> encode_result())

Specs

list_codec(a) :: codec([a])

Specs

remaining_bits() :: bitstring()

Specs

type() :: any()

Specs

type2() :: any()

Link to this section Functions

Link to this function

append(list_codec, codec)

View Source

Specs

append(list_codec(type()), codec(type())) :: list_codec(type())

Appends a list codec with a codec to form a new list.

Examples

iex> codec = list_of(3, byte()) |> append(bits(4))
...> [1, 2, 3, 4] |> encode(codec)
{:ok, <<1, 2, 3, 4::4>>}
...> <<1, 2, 3, 4::4>> |> decode(codec)
{:ok, [1, 2, 3, 4], <<>>}

Specs

bit() :: codec(0 | 1)

Encodes/decodes a single bit as either 0 or 1.

Examples

iex> <<1::1>> |> decode(bit())
{:ok, 1, <<>>}

iex> <<0::1>> |> decode(bit())
{:ok, 0, <<>>}

iex> 1 |> encode(bit())
{:ok, <<1::1>>}

iex> 0 |> encode(bit())
{:ok, <<0::1>>}

iex> 2 |> encode(bit())
{:error, "2 cannot be encoded in 1 bits"}

Specs

Encodes/decodes a series of bits as an unsigned integer.

Examples

iex> <<3::7>> |> decode(bits(7))
{:ok, 3, <<>>}

iex> 5 |> encode(bits(7))
{:ok, <<5::7>>}

iex> 300 |> encode(bits(7))
{:error, "300 cannot be encoded in 7 bits"}

Specs

bits_remaining() :: boolean_codec()

Specs

bool_bit() :: boolean_codec()

Encodes/decodes a single bit as either true (if 1) or false (if 0).

Examples

iex> <<1::1>> |> decode(bool_bit())
{:ok, true, <<>>}

iex> <<0::1>> |> decode(bool_bit())
{:ok, false, <<>>}

iex> true |> encode(bool_bit())
{:ok, <<1::1>>}

iex> false |> encode(bool_bit())
{:ok, <<0::1>>}

Specs

byte() :: codec(non_neg_integer())

Encodes/decodes a single byte as an unsigned integer.

Examples

iex> <<123>> |> decode(byte())
{:ok, 123, <<>>}

iex> 210 |> encode(byte())
{:ok, <<210>>}

iex> 400 |> encode(byte())
{:error, "400 cannot be encoded in 8 bits"}

Specs

Encodes/decodes a series of bytes as an unsigned integer.

Examples

iex> <<1, 2>> |> decode(bytes(2))
{:ok, 258, <<>>}

iex> 258 |> encode(bytes(2))
{:ok, <<1, 2>>}

Specs

choice([codec(type())]) :: codec(type())

Tries to encode/decode each codec in succession, using the first one that succeeds.

Examples

iex> <<1>> |> decode(choice([byte(), bits(4)]))
{:ok, 1, <<>>}

iex> <<1::4>> |> decode(choice([byte(), bits(4)]))
{:ok, 1, <<>>}

iex> <<1::2>> |> decode(choice([byte(), bits(4)]))
{:error, "None of the choices worked", <<1::2>>}

Specs

combine(codec(type()), codec(type2())) :: codec({type(), type2()})

Combines two codecs into a codec that produces a 2-element tuple of each value.

Examples

iex> {198, 2} |> encode(combine(byte(), byte()))
{:ok, <<198, 2>>}

iex> <<198, 2>> |> decode(combine(byte(), byte()))
{:ok, {198, 2}, <<>>}

Specs

cons(codec(type()), list_codec(type())) :: list_codec(type())

Combines a codec with a list codec to form a new list.

Examples

iex> non_empty_list = byte() |> cons(list(byte()))
...> [1] |> encode(non_empty_list)
{:ok, <<1>>}
...> [] |> encode(non_empty_list)
{:error, "Failed to encode []"}
...> <<1>> |> decode(non_empty_list)
{:ok, [1], <<>>}
...> <<>> |> decode(non_empty_list)
{:error, "Could not decode 8 bits from \"\"", <<>>}

Specs

constant(type(), bitstring()) :: codec(type())

A codec that always encodes to and decodes from the same value.

It fails if it doesn't see the expected value that it always encodes/decodes.

Examples

iex> <<200>> |> decode(constant(10, <<200>>))
{:ok, 10, <<>>}

iex> <<234>> |> decode(constant(10, <<200>>))
{:error, "<<234>> did not equal <<200>> in constant", <<234>>}

iex> 10 |> encode(constant(10, <<200>>))
{:ok, <<200>>}

iex> 22 |> encode(constant(10, <<200>>))
{:error, "22 did not equal 10 in constant"}
Link to this function

convert(codec, convert_to, convert_from)

View Source

Specs

convert(codec(type()), (type() -> type2()), (type2() -> type())) ::
  codec(type2())

Used to convert a codec to another type.

Must also be used with caution to make sure your conversion is idempotent.

Examples

defmodule Foo do
  defstruct a: nil, b: nil
end

iex> tuple_codec = combine(byte(), byte())
...> struct_codec = tuple_codec |> convert(fn {a, b} -> %Foo{a: a, b: b} end, fn %Foo{a: a, b: b} -> {a, b} end)
...> <<12, 34>> |> decode(struct_codec)
{:ok, %Foo{a: 12, b: 34}, <<>>}
...> %Foo{a: 12, b: 34} |> encode(struct_codec)
{:ok, <<12, 34>>}

Specs

create(encoder(type()), decoder(type())) :: %Codex{
  decode: term(),
  encode: term()
}

Creates a new codec.

It is recommended to use the other well-tested functions in the module to build a codec instead of creating your own unless you absolutely have to.

When creating your own codec it MUST be idempotent.

Examples

iex> Codex.create(fn _ -> {:ok, <<>>} end, fn
...>   <<>> -> {:ok, false, <<>>}
...>   bits -> {:ok, true, bits}
...> end)
#Codex<...>

Specs

decode(bitstring(), codec(type())) :: decode_result(type())

Decodes a value from its binary form using the supplied codec.

The third element of the tuple is any remaining unparsed bits.

Examples

iex> <<198>> |> decode(byte())
{:ok, 198, <<>>}

Specs

done(codec(type())) :: codec(type())

Only succeeds if there were no bits left to parse.

Examples

iex> <<10>> |> decode(byte() |> done())
{:ok, 10, <<>>}

iex> <<10, 11>> |> decode(byte() |> done())
{:error, "There was more to parse", <<11>>}

Specs

empty() :: codec([])

A codec that always decodes to an empty list, [].

It can never fail decoding.

Examples

iex> defmodule EmptyExample do
...>   def listify([]), do: empty()
...>   def listify([codec | rest]), do: codec |> cons(listify(rest))
...> end
...> <<1>> |> decode(EmptyExample.listify([]))
{:ok, [], <<1>>}
...> <<22, 38>> |> decode(EmptyExample.listify([byte(), byte()]))
{:ok, [22, 38], <<>>}

Specs

encode(type(), codec(type())) :: encode_result()

Encodes a value to its binary form using the supplied codec.

Examples

iex> 198 |> encode(byte())
{:ok, <<198>>}
Link to this function

ensure(codec, predicate, error)

View Source

Specs

ensure(codec(type()), (type() -> boolean()), String.t()) :: codec(type())

Fails a codec if the result doesn't satisfy the predicate.

Examples

iex> codec = byte() |> ensure(& &1 > 10, "Must be greater than 10")
...> 5 |> encode(codec)
{:error, "Must be greater than 10"}
...> 11 |> encode(codec)
{:ok, <<11>>}
...> <<5>> |> decode(codec)
{:error, "Must be greater than 10"}
...> <<11>> |> decode(codec)
{:ok, 11, <<>>}

Specs

fail(String.t()) :: codec(any())

Creates a codec that always fails with the supplied error message.

This is not useful on its own but can be useful when building other codecs.

Examples

iex> defmodule FailExample do
...>   def choose([]), do: fail("None of the choices worked")
...>   def choose([codec | rest]), do: fallback(codec, choice(rest))
...> end
...> codec = FailExample.choose([byte(), bits(4)])
...> <<1>> |> decode(codec)
{:ok, 1, <<>>}
...> <<1::2>> |> decode(codec)
{:error, "None of the choices worked", <<1::2>>}
Link to this function

fail(encode_error, decode_error)

View Source

Specs

fail(String.t(), String.t()) :: codec(any())

Creates a codec that always fails with the supplied error messages.

Same as fail/1 but you can supply a different error message for encoding and decoding.

Link to this function

fallback(codec1, codec2)

View Source

Specs

fallback(codec(type()), codec(type2())) :: codec(type() | type2())

Uses the first codec, falling back to the second if it fails.

Examples

iex> optional_byte = byte() |> fallback(nothing())
...> <<8>> |> decode(optional_byte)
{:ok, 8, <<>>}
...> <<8::4>> |> decode(optional_byte)
{:ok, nil, <<8::4>>}
Link to this function

join(codec, group_size \\ 1)

View Source

Specs

Link to this function

length_prefixed(length_codec, codec)

View Source

Specs

length_prefixed(codec(non_neg_integer()), codec(type())) :: list_codec(type())

Specs

list(codec(type())) :: list_codec(type())

Specs

list_of(non_neg_integer(), codec(type())) :: list_codec(type())

Decodes a list of codec of length count.

Examples

iex> [1, 2, 3, 4] |> encode(list_of(4, byte()))
{:ok, <<1, 2, 3, 4>>}

iex> <<1, 2, 3, 4>> |> decode(list_of(4, byte()))
{:ok, [1, 2, 3, 4], <<>>}

Specs

lookahead(codec(any())) :: boolean_codec()

Tests whether a codec would succeed. Decodes to true if it would, false otherwise.

Similar to recover/1 except it doesn't consume the bits when it succeeds and returns true.

Examples

iex> <<38>> |> decode(lookahead(byte()))
{:ok, true, <<38>>}

iex> <<1::1>> |> decode(lookahead(byte()))
{:ok, false, <<1::1>>}
Link to this function

map_list(codec, map, map_back)

View Source

Specs

map_list(list_codec(type()), (type() -> type2()), (type2() -> type())) ::
  list_codec(type2())

Specs

mapping(codec(type()), %{required(type()) => type2()}) :: codec(type2())
Link to this function

mapping(codec, mapping, map_back)

View Source

Specs

mapping(codec(type()), %{required(type()) => type2()}, %{
  required(type()) => type2()
}) :: codec(type2())

Specs

non_empty_list(codec(type())) :: codec([type(), ...])

Specs

Inverts a boolean codec.

Examples

iex> <<38>> |> decode(not_(recover(byte())))
{:ok, false, <<>>}

iex> <<1::1>> |> decode(not_(recover(byte())))
{:ok, true, <<1::1>>}

Specs

nothing() :: codec(nil)

A codec that always decodes to nil.

It can never fail decoding.

Examples

iex> optional_byte = byte() |> fallback(nothing())
...> <<8>> |> decode(optional_byte)
{:ok, 8, <<>>}
...> <<8::4>> |> decode(optional_byte)
{:ok, nil, <<8::4>>}

Specs

optional(codec(type())) :: codec(type() | nil)

Creates a codec that might not work. If it fails decoding it returns nil instead.

Examples

iex> <<11>> |> decode(optional(byte()))
{:ok, 11, <<>>}

iex> <<1::4>> |> decode(optional(byte()))
{:ok, nil, <<1::4>>}

Specs

pad(codec(type()), non_neg_integer()) :: codec(type())

Pads the bitstring before decoding.

Examples

iex> <<1::1>> |> decode(bit |> pad(2))
{:ok, 4, <<>>}

# It's as if you padded it with two zero bits on the right:
iex> <<1::1, 0::1, 0::1>> |> decode(bits(3))
{:ok, 4, <<>>}

iex> 4 |> encode(bit() |> pad(2))
{:ok, <<1::1>>}

Specs

peek(codec(type())) :: codec(type())

Creates a codec that decodes without actually consuming the bits.

Examples

iex> <<4>> |> decode(peek(byte()))
{:ok, 4, <<4>>}

iex> codec = peek(byte()) |> combine(byte())
...> <<4>> |> decode(codec)
{:ok, {4, 4}, <<>>}
...> {4, 4} |> encode(codec)
{:ok, <<4>>}

Specs

recover(codec(any())) :: boolean_codec()

Tests whether a codec would succeed. Decodes to true if it would, false otherwise.

Examples

iex> <<38>> |> decode(recover(byte()))
{:ok, true, <<>>}

iex> <<1::1>> |> decode(recover(byte()))
{:ok, false, <<1::1>>}
Link to this function

refute(codec, predicate, error)

View Source

Specs

refute(codec(type()), (type() -> boolean()), String.t()) :: codec(type())

Fails a codec if the result satisfies the predicate.

Examples

iex> codec = byte() |> refute(& &1 > 10, "Can't be greater than 10")
...> 11 |> encode(codec)
{:error, "Can't be greater than 10"}
...> 5 |> encode(codec)
{:ok, <<5>>}
...> <<11>> |> decode(codec)
{:error, "Can't be greater than 10"}
...> <<5>> |> decode(codec)
{:ok, 5, <<>>}

Specs

reverse(list_codec(type())) :: list_codec(type())

Reverses a list codec.

Examples

iex> codec = list(byte()) |> reverse()
...> [1, 2, 3] |> encode(codec)
{:ok, <<3, 2, 1>>}
...> <<3, 2, 1>> |> decode(codec)
{:ok, [1, 2, 3], <<>>}

Specs

sequence([codec(type())]) :: list_codec(type())

Combines a list of codecs into a single codec that produces a list of those values.

Examples

iex> codec = sequence([byte(), byte(), byte()])
...> <<0x10, 0xFF, 0xAB>> |> decode(codec)
{:ok, [16, 255, 171], <<>>}
...> [16, 255, 171] |> encode(codec)
{:ok, <<0x10, 0xFF, 0xAB>>}
Link to this function

take_until(codec, boolean_codec)

View Source

Specs

take_until(codec(type()), boolean_codec()) :: list_codec(type())
Link to this function

take_while(boolean_codec, codec)

View Source

Specs

take_while(boolean_codec(), codec(type())) :: list_codec(type())

Specs

then(codec(type()), (type() -> codec(type2())), (type2() -> type())) ::
  codec(type2())

Use the result of a codec to create the next codec.

Examples

iex> length = byte()
...> length_prefixed = length |> then(&list_of(&1, byte()), &length/1)
...> <<4, 1, 2, 3, 4>> |> decode(length_prefixed)
{:ok, [1, 2, 3, 4], <<>>}
...> [1, 2, 3, 4] |> encode(length_prefixed)
{:ok, <<4, 1, 2, 3, 4>>}

Specs

value(type()) :: codec(type())

A codec that always decodes to a certain value.

It never consumes bits while decoding so it can't fail. It can fail on encoding if the value to encode doesn't match.

Examples

iex> <<>> |> decode(value(10))
{:ok, 10, <<>>}

iex> <<200>> |> decode(value(10))
{:ok, 10, <<200>>}

iex> 10 |> encode(value(10))
{:ok, <<>>}

iex> 22 |> encode(value(10))
{:error, "22 did not equal 10 in constant"}