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

Exclude default packers #55

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
use Mix.Config

config :msgpax, exclude_packers: []

case System.get_env("TEST_EXCLUDE", "") do
"" ->
nil
other ->
config :msgpax, exclude_packers: String.split(other, ",") |> Enum.map(& &1 |> String.to_atom() )
end

# import_config "#{Mix.env()}.exs"
9 changes: 9 additions & 0 deletions lib/msgpax.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ defmodule Msgpax do
`#Msgpax.Ext<4, "02:12">` | extension | `#Msgpax.Ext<4, "02:12">`
`#DateTime<2017-12-06 00:00:00Z>` | extension | `#DateTime<2017-12-06 00:00:00Z>`

## Excluding Default Packers

Default packers can be excluded by passing a config option:
`config: :msgpack, exclude_packers: [:atom, :float]`

The packers that can be exlucded are: :atom, :bitstring, :map, :list, :float, :integer, and :bin

This can be used to override default behaviors, like serializing to float32s. Use this sparingly as it's global and can break things.

"""

alias __MODULE__.Packer
Expand Down
192 changes: 105 additions & 87 deletions lib/msgpax/packer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -105,131 +105,149 @@ defprotocol Msgpax.Packer do
def pack(term)
end

defimpl Msgpax.Packer, for: Atom do
def pack(nil), do: [0xC0]
def pack(false), do: [0xC2]
def pack(true), do: [0xC3]

def pack(atom) do
atom
|> Atom.to_string()
|> @protocol.BitString.pack()
require MsgPax.PackerMacros

MsgPax.PackerMacros.excluded?(:atom) do
defimpl Msgpax.Packer, for: Atom do
def pack(nil), do: [0xC0]
def pack(false), do: [0xC2]
def pack(true), do: [0xC3]

def pack(atom) do
atom
|> Atom.to_string()
|> @protocol.BitString.pack()
end
end
end

defimpl Msgpax.Packer, for: BitString do
def pack(binary) when is_binary(binary) do
[format(binary) | binary]
end
MsgPax.PackerMacros.excluded?(:bitstring) do
defimpl Msgpax.Packer, for: BitString do
def pack(binary) when is_binary(binary) do
[format(binary) | binary]
end

def pack(bits) do
throw({:not_encodable, bits})
end
def pack(bits) do
throw({:not_encodable, bits})
end

defp format(binary) do
size = byte_size(binary)
defp format(binary) do
size = byte_size(binary)

cond do
size < 32 -> 0b10100000 + size
size < 256 -> [0xD9, size]
size < 0x10000 -> <<0xDA, size::16>>
size < 0x100000000 -> <<0xDB, size::32>>
true -> throw({:too_big, binary})
cond do
size < 32 -> 0b10100000 + size
size < 256 -> [0xD9, size]
size < 0x10000 -> <<0xDA, size::16>>
size < 0x100000000 -> <<0xDB, size::32>>
true -> throw({:too_big, binary})
end
end
end
end

defimpl Msgpax.Packer, for: Map do
defmacro __deriving__(module, struct, options) do
@protocol.Any.deriving(module, struct, options)
end
MsgPax.PackerMacros.excluded?(:map) do
defimpl Msgpax.Packer, for: Map do
defmacro __deriving__(module, struct, options) do
@protocol.Any.deriving(module, struct, options)
end

def pack(map) do
[format(map) | map |> Map.to_list() |> pack([])]
end
def pack(map) do
[format(map) | map |> Map.to_list() |> pack([])]
end

defp pack([{key, value} | rest], result) do
pack(rest, [@protocol.pack(key), @protocol.pack(value) | result])
end
defp pack([{key, value} | rest], result) do
pack(rest, [@protocol.pack(key), @protocol.pack(value) | result])
end

defp pack([], result), do: result
defp pack([], result), do: result

defp format(map) do
length = map_size(map)
defp format(map) do
length = map_size(map)

cond do
length < 16 -> 0b10000000 + length
length < 0x10000 -> <<0xDE, length::16>>
length < 0x100000000 -> <<0xDF, length::32>>
true -> throw({:too_big, map})
cond do
length < 16 -> 0b10000000 + length
length < 0x10000 -> <<0xDE, length::16>>
length < 0x100000000 -> <<0xDF, length::32>>
true -> throw({:too_big, map})
end
end
end
end

defimpl Msgpax.Packer, for: List do
def pack(list) do
[format(list) | list |> Enum.reverse() |> pack([])]
end
MsgPax.PackerMacros.excluded?(:list) do
defimpl Msgpax.Packer, for: List do
def pack(list) do
[format(list) | list |> Enum.reverse() |> pack([])]
end

defp pack([item | rest], result) do
pack(rest, [@protocol.pack(item) | result])
end
defp pack([item | rest], result) do
pack(rest, [@protocol.pack(item) | result])
end

defp pack([], result), do: result
defp pack([], result), do: result

defp format(list) do
length = length(list)
defp format(list) do
length = length(list)

cond do
length < 16 -> 0b10010000 + length
length < 0x10000 -> <<0xDC, length::16>>
length < 0x100000000 -> <<0xDD, length::32>>
true -> throw({:too_big, list})
cond do
length < 16 -> 0b10010000 + length
length < 0x10000 -> <<0xDC, length::16>>
length < 0x100000000 -> <<0xDD, length::32>>
true -> throw({:too_big, list})
end
end
end
end

defimpl Msgpax.Packer, for: Float do
def pack(num) do
<<0xCB, num::64-float>>
MsgPax.PackerMacros.excluded?(:float) do
defimpl Msgpax.Packer, for: Float do

def pack(num) do
<<0xCB, num::64-float>>
end
end
end

defimpl Msgpax.Packer, for: Integer do
def pack(int) when int < 0 do
cond do
int >= -32 -> [0x100 + int]
int >= -128 -> [0xD0, 0x100 + int]
int >= -0x8000 -> <<0xD1, int::16>>
int >= -0x80000000 -> <<0xD2, int::32>>
int >= -0x8000000000000000 -> <<0xD3, int::64>>
true -> throw({:too_big, int})
MsgPax.PackerMacros.excluded?(:integer) do
defimpl Msgpax.Packer, for: Integer do
@spec pack(any) :: <<_::24, _::_*16>> | [...]
def pack(int) when int < 0 do
cond do
int >= -32 -> [0x100 + int]
int >= -128 -> [0xD0, 0x100 + int]
int >= -0x8000 -> <<0xD1, int::16>>
int >= -0x80000000 -> <<0xD2, int::32>>
int >= -0x8000000000000000 -> <<0xD3, int::64>>
true -> throw({:too_big, int})
end
end
end

def pack(int) do
cond do
int < 128 -> [int]
int < 256 -> [0xCC, int]
int < 0x10000 -> <<0xCD, int::16>>
int < 0x100000000 -> <<0xCE, int::32>>
int < 0x10000000000000000 -> <<0xCF, int::64>>
true -> throw({:too_big, int})
def pack(int) do
cond do
int < 128 -> [int]
int < 256 -> [0xCC, int]
int < 0x10000 -> <<0xCD, int::16>>
int < 0x100000000 -> <<0xCE, int::32>>
int < 0x10000000000000000 -> <<0xCF, int::64>>
true -> throw({:too_big, int})
end
end
end
end

defimpl Msgpax.Packer, for: Msgpax.Bin do
def pack(%{data: data}) when is_binary(data), do: [format(data) | data]
MsgPax.PackerMacros.excluded?(:bin) do
defimpl Msgpax.Packer, for: Msgpax.Bin do
def pack(%{data: data}) when is_binary(data), do: [format(data) | data]

defp format(binary) do
size = byte_size(binary)
defp format(binary) do
size = byte_size(binary)

cond do
size < 256 -> [0xC4, size]
size < 0x10000 -> <<0xC5, size::16>>
size < 0x100000000 -> <<0xC6, size::32>>
true -> throw({:too_big, binary})
cond do
size < 256 -> [0xC4, size]
size < 0x10000 -> <<0xC5, size::16>>
size < 0x100000000 -> <<0xC6, size::32>>
true -> throw({:too_big, binary})
end
end
end
end
Expand Down
8 changes: 8 additions & 0 deletions lib/msgpax/packer_macros.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@

defmodule MsgPax.PackerMacros do
defmacro excluded?(packer_type, do: block) do
unless packer_type in Application.get_env(:msgpax, :exclude_packers, []) do
block # AST as by quote do: unquote(block)
end
end
end
6 changes: 3 additions & 3 deletions lib/msgpax/plug_parser.ex
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
if Code.ensure_compiled?(Plug) do
if Code.ensure_compiled(Plug) == {:module, Plug} do
defmodule Msgpax.PlugParser do
@moduledoc """
A `Plug.Parsers` plug for parsing a MessagePack-encoded body.
Expand Down Expand Up @@ -81,15 +81,15 @@ if Code.ensure_compiled?(Plug) do
when is_atom(module) and is_atom(function) and is_list(extra_args) do
arity = length(extra_args) + 1

unless Code.ensure_compiled?(module) and function_exported?(module, function, arity) do
unless Code.ensure_compiled(module) == {:module, module} and function_exported?(module, function, arity) do
raise ArgumentError,
"invalid :unpacker option. Undefined function " <>
Exception.format_mfa(module, function, arity)
end
end

defp validate_unpacker!(unpacker) when is_atom(unpacker) do
unless Code.ensure_compiled?(unpacker) do
unless Code.ensure_compiled(unpacker) == {:module, unpacker} do
raise ArgumentError,
"invalid :unpacker option. The module #{inspect(unpacker)} is not " <>
"loaded and could not be found"
Expand Down