Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce overridable protocol implementations #68

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 3.0.0 - [TBD]
* Added the `Msgpax.Unpacker` protocol.
* Refactored how extensions should be packed and unpacked.
* Added default extension implementation for `Date`.
* Added possibility of overwriting protocols through the use of `Mgpax.Ext.defimpl`.

__Breaking changes:__
* Packing and unpacking of extensions changed, as there is no more `Msgpax.Ext` struct. Please refer to `Msgpax.Unpacker` docs for more details and examples.
* The `Msgpax.Packer.pack` protocol now expects the `/2` arity to be defined, as the `defimpl` macro no longer introduces a catch-all clause.
* `Msgpax.defimpl` has been moved to `Msgpax.Ext.defimpl`.
* `Date` now has a default implementation using extension type 101.

## v2.5.0 – [TBD]
* Upgraded `Msgpax.Packer` protocol so that the pack function can receive options.

__Breaking changes:__

* `Msgpax.Packer.pack/1` changed to `Msgpax.Packer.pack/2`, so all protocol
implementations should be updated. See `Msgpax.defimpl/3` for examples.

## v2.4.0 – 2023-05-27

* Dropped support for Elixir versions before 1.6.
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ Msgpax is a high-performance and comprehensive library for serializing and deser
* Packing and unpacking Elixir terms via [`Msgpax.pack/1`][docs-msgpax-pack-1] and [`Msgpax.unpack/1`][docs-msgpax-unpack-1] (and their bang! variants).
* Unpacking of partial slices of MessagePack-encoded terms via [`Msgpax.unpack_slice/1`][docs-msgpax-unpack_slice-1].
* Support for "Binary" and "Extension" MessagePack types via [`Msgpax.Bin`][docs-msgpax-bin] and [`Msgpax.Ext`][docs-msgpax-ext], respectively.
* Protocol-based packing through the [`Msgpax.Packer`][docs-msgpax-packer] protocol, that can be derived for user-defined structs.
* Protocol-based packing through the [`Msgpax.Packer`][docs-msgpax-packer] and [`Msgpax.Unpacker`][docs-msgpax-unpacker] protocols, that can be derived for user-defined structs.
* A Plug parser ([`Msgpax.PlugParser`][docs-msgpax-plug-parser]) to parse requests with MessagePack-encoded bodies.
* Support for MessagePack data fragment manipulation.

Expand All @@ -24,7 +24,7 @@ Add `:msgpax` as a dependency in your `mix.exs` file:

```elixir
def deps do
[{:msgpax, "~> 2.0"}]
[{:msgpax, "~> 3.0"}]
end
```

Expand All @@ -41,6 +41,7 @@ Msgpax is released under [the ISC license](LICENSE).
[docs-msgpax-unpack-1]: http://hexdocs.pm/msgpax/Msgpax.html#unpack/1
[docs-msgpax-unpack_slice-1]: http://hexdocs.pm/msgpax/Msgpax.html#unpack_slice/1
[docs-msgpax-packer]: http://hexdocs.pm/msgpax/Msgpax.Packer.html
[docs-msgpax-unpacker]: http://hexdocs.pm/msgpax/Msgpax.Unpacker.html
[docs-msgpax-bin]: http://hexdocs.pm/msgpax/Msgpax.Bin.html
[docs-msgpax-ext]: http://hexdocs.pm/msgpax/Msgpax.Ext.html
[docs-msgpax-plug-parser]: http://hexdocs.pm/msgpax/Msgpax.PlugParser.html
26 changes: 17 additions & 9 deletions lib/msgpax.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,17 @@ defmodule Msgpax do
`%{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">`
`#Msgpax.Ext0<"02:12">`⁴ | extension | `#Msgpax.Ext0<"02:12">`
`#Date<2017-12-06>`⁵ | extension | `#Date<2017-12-06>`
`#DateTime<2017-12-06 00:00:00Z>` | extension | `#DateTime<2017-12-06 00:00:00Z>`

¹ `Msgpax.Packer` provides helper functions to facilitate the serialization of natively unsupported data types.

² NaN and ±infinity are not enabled by default. See `unpack/2` for for more information.

³ To deserialize back to `Msgpax.Bin` structs see the `unpack/2` options.

⁴ There are 128 extension ranging from `Msgpax.Ext0` to `Msgpax.Ext127`
"""

alias __MODULE__.Packer
Expand All @@ -57,6 +60,7 @@ defmodule Msgpax do
* `:iodata` - (boolean) if `true`, this function returns the encoded term as
iodata, if `false` as a binary. Defaults to `true`.

Any other options are passed to `Msgpax.Packer.pack/2`.
## Examples

iex> {:ok, packed} = Msgpax.pack("foo")
Expand All @@ -72,10 +76,10 @@ defmodule Msgpax do
"""
@spec pack(term, Keyword.t()) :: {:ok, iodata} | {:error, Msgpax.PackError.t() | Exception.t()}
def pack(term, options \\ []) when is_list(options) do
iodata? = Keyword.get(options, :iodata, true)
{iodata?, remaining_options} = Keyword.pop(options, :iodata, true)

try do
Packer.pack(term)
Packer.pack(term, remaining_options)
catch
:throw, reason ->
{:error, %Msgpax.PackError{reason: reason}}
Expand Down Expand Up @@ -209,15 +213,19 @@ defmodule Msgpax do
{:error, %Msgpax.UnpackError{reason: {:invalid_format, 163}}}

"""
@spec unpack_slice(iodata, Keyword.t()) :: {:ok, any, binary} | {:error, Msgpax.UnpackError.t()}
@spec unpack_slice(iodata, Keyword.t()) ::
{:ok, any, binary} | {:error, Msgpax.UnpackError.t() | Exception.t()}
def unpack_slice(iodata, options \\ []) when is_list(options) do
try do
iodata
|> IO.iodata_to_binary()
|> Unpacker.unpack(options)
|> Unpacker.Helper.unpack(options)
catch
:throw, reason ->
{:error, %Msgpax.UnpackError{reason: reason}}

:error, %Protocol.UndefinedError{protocol: Msgpax.Unpacker} = exception ->
{:error, exception}
else
{value, rest} ->
{:ok, value, rest}
Expand Down Expand Up @@ -266,13 +274,12 @@ defmodule Msgpax do
* `:binary` - (boolean) if `true`, then binaries are decoded as `Msgpax.Bin`
structs instead of plain Elixir binaries. Defaults to `false`.

* `:ext` - (module) a module that implements the `Msgpax.Ext.Unpacker`
behaviour. For more information, see the docs for `Msgpax.Ext.Unpacker`.

* `:nonfinite_floats` - (boolean) if `true`, deserializes NaN and ±infinity to
"signalling" atoms (see the "Data conversion" section), otherwise errors.
Defaults to `false`.

Any other options are passed to `Msgpax.Unpacker.unpack/2`.

## Examples

iex> Msgpax.unpack(<<163, "foo">>)
Expand All @@ -287,7 +294,8 @@ defmodule Msgpax do
#Msgpax.Bin<<<3, 18, 122, 27, 115>>>

"""
@spec unpack(iodata, Keyword.t()) :: {:ok, any} | {:error, Msgpax.UnpackError.t()}
@spec unpack(iodata, Keyword.t()) ::
{:ok, any} | {:error, Msgpax.UnpackError.t() | Exception.t()}
def unpack(iodata, options \\ []) do
case unpack_slice(iodata, options) do
{:ok, value, <<>>} ->
Expand Down