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
Specs
Specs
decode_result(a) :: {:ok, a, remaining_bits()} | {:error, String.t(), remaining_bits()}
Specs
decoder(a) :: (bitstring() -> decode_result(a))
Specs
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
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
bits(non_neg_integer()) :: codec(non_neg_integer())
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
bytes(non_neg_integer()) :: codec(non_neg_integer())
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
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
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
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"}
Specs
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
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
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>>}
Specs
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
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>>}
Specs
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.
Specs
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>>}
Specs
join(list_codec(binary()), pos_integer()) :: codec(binary())
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>>}
Specs
map_list(list_codec(type()), (type() -> type2()), (type2() -> type())) :: list_codec(type2())
Specs
Specs
Specs
Specs
not_(boolean_codec()) :: boolean_codec()
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
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
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>>}
Specs
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>>}
Specs
take_until(codec(type()), boolean_codec()) :: list_codec(type())
Specs
take_while(boolean_codec(), codec(type())) :: list_codec(type())
Specs
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
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"}