Current section

Files

Jump to
msgpax lib msgpax.ex
Raw

lib/msgpax.ex

defmodule Msgpax do
@moduledoc """
This module provides functions for serializing and de-serializing Elixir terms
using the [MessagePack](http://msgpack.org/) format.
## Data conversion
The following table shows how Elixir types are serialized to MessagePack types
and how MessagePack types are de-serialized back to Elixir types.
Elixir | MessagePack | Elixir
------------------------------ | ------------- | -------------
`nil` | nil | `nil`
`true` | boolean | `true`
`false` | boolean | `false`
`-1` | integer | `-1`
`1.25` | float | `1.25`
`:ok` | string | `"ok"`
`Atom` | string | `"Elixir.Atom"`
`"str"` | string | `"str"`
`"\xFF\xFF"` | string | `"\xFF\xFF"`
`#Msgpax.Bin<"\xFF">` | binary | `"\xFF"`
`%{foo: "bar"}` | map | `%{"foo" => "bar"}`
`[foo: "bar"]` | map | `%{"foo" => "bar"}`
`[1, true]` | array | `[1, true]`
`#Msgpax.Ext<4, "02:12">` | extension | `#Msgpax.Ext<4, "02:12">`
"""
alias __MODULE__.Packer
alias __MODULE__.Unpacker
@doc """
Serializes `term`.
This function returns iodata; if you want to force the result to be a binary,
you can use `IO.iodata_to_binary/1`.
This function returns `{:ok, iodata}` if the serialization is sucessful,
`{:error, reason}` otherwise. Reason can be:
* `{:bad_arg, term}` - means that the given argument is not serializable. For
example, this is returned when you try to pack bits instead of a binary
(as only binaries can be serialized).
* `{:too_big, term}` - means that the given term is too big to be
encoded. What "too big" means depends on the term being encoded; for
example, integers larger than `18446744073709551616` are too big to be
encoded with MessagePack.
## Examples
iex> {:ok, packed} = Msgpax.pack("foo")
iex> IO.iodata_to_binary(packed)
<<163, 102, 111, 111>>
iex> Msgpax.pack(20000000000000000000)
{:error, {:too_big, 20000000000000000000}}
"""
def pack(term) do
Packer.pack(term)
end
@doc """
Works as `pack/1`, but raises if there's an error.
This function works like `pack/1`, except it returns the `term` (instead of
`{:ok, term}`) if the serialization is successful and raises a
`Msgpax.PackError` exception otherwise.
## Examples
iex> "foo" |> Msgpax.pack!() |> IO.iodata_to_binary()
<<163, 102, 111, 111>>
iex> Msgpax.pack!(20000000000000000000)
** (Msgpax.PackError) too big value: 20000000000000000000
"""
def pack!(term) do
Packer.pack!(term)
end
@doc """
De-serializes part of the given `iodata`.
This function works like `unpack/2`, but instead of requiring the input to be
a MessagePack-serialized term with nothing after that, it accepts leftover
bytes at the end of `iodata` and only de-serializes the part of the input that
makes sense. It returns `{:ok, term, rest}` if de-serialization is successful,
`{:error, reason}` otherwise.
See `unpack/2` for more information on the supported options.
## Examples
iex> Msgpax.unpack_slice(<<163, "foo", "junk">>)
{:ok, "foo", "junk"}
iex> Msgpax.unpack_slice(<<163, "fo">>)
{:error, {:bad_format, 163}}
"""
def unpack_slice(iodata, opts \\ %{}) do
Unpacker.unpack(iodata, opts)
end
@doc """
Works like `unpack_slice/2` but raises in case of error.
This function works like `unpack_slice/2`, but returns just `{term, rest}` if
de-serialization is successful and raises a `Msgpax.UnpackError` exception if
it's not.
## Examples
iex> Msgpax.unpack_slice!(<<163, "foo", "junk">>)
{"foo", "junk"}
iex> Msgpax.unpack_slice!(<<163, "fo">>)
** (Msgpax.UnpackError) bad format: 163
"""
def unpack_slice!(iodata, opts \\ %{}) do
Unpacker.unpack!(iodata, opts)
end
@doc """
De-serializes the given `iodata`.
This function de-serializes the given `iodata` into an Elixir term. It returns
`{:ok, term}` if de-serialization is successful, `{:error, reason}` otherwise.
## Options
* `:binary` - (boolean) if `true`, then binaries are decoded as `Msgpax.Bin`
structs instead of plain Elixir binaries.
## Examples
iex> Msgpax.unpack(<<163, "foo">>)
{:ok, "foo"}
iex> Msgpax.unpack(<<163, "foo", "junk">>)
{:error, {:extra_bytes, "junk"}}
iex> packed = Msgpax.pack!(Msgpax.Bin.new(<<3, 18, 122, 27, 115>>))
iex> {:ok, bin} = Msgpax.unpack(packed, %{binary: true})
iex> bin
#Msgpax.Bin<<<3, 18, 122, 27, 115>>>
"""
def unpack(iodata, opts \\ %{}) do
case unpack_slice(iodata, opts) do
{:ok, value, <<>>} ->
{:ok, value}
{:ok, _, bytes} ->
{:error, {:extra_bytes, bytes}}
{:error, _} = error ->
error
end
end
@doc """
Works like `unpack/2`, but raises in case of errors.
This function works like `unpack/2`, but it returns `term` (instead of `{:ok,
term}`) if de-serialization is successful, otherwise raises a
`Msgpax.UnpackError` exception.
## Example
iex> Msgpax.unpack!(<<163, "foo">>)
"foo"
iex> Msgpax.unpack!(<<163, "foo", "junk">>)
** (Msgpax.UnpackError) extra bytes follow after packet: "junk"
iex> packed = Msgpax.pack!(Msgpax.Bin.new(<<3, 18, 122, 27, 115>>))
iex> Msgpax.unpack!(packed, %{binary: true})
#Msgpax.Bin<<<3, 18, 122, 27, 115>>>
"""
def unpack!(iodata, opts \\ %{}) do
case unpack(iodata, opts) do
{:ok, value} -> value
{:error, reason} ->
raise Msgpax.UnpackError, reason: reason
end
end
end