diff --git a/config/config.exs b/config/config.exs new file mode 100644 index 0000000..aa31f17 --- /dev/null +++ b/config/config.exs @@ -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" diff --git a/lib/msgpax.ex b/lib/msgpax.ex index d1f9b33..290a5b0 100644 --- a/lib/msgpax.ex +++ b/lib/msgpax.ex @@ -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 diff --git a/lib/msgpax/packer.ex b/lib/msgpax/packer.ex index 6c11456..7ff4aea 100644 --- a/lib/msgpax/packer.ex +++ b/lib/msgpax/packer.ex @@ -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 diff --git a/lib/msgpax/packer_macros.ex b/lib/msgpax/packer_macros.ex new file mode 100644 index 0000000..6e3b146 --- /dev/null +++ b/lib/msgpax/packer_macros.ex @@ -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 diff --git a/lib/msgpax/plug_parser.ex b/lib/msgpax/plug_parser.ex index 62ee0e3..3adb68b 100644 --- a/lib/msgpax/plug_parser.ex +++ b/lib/msgpax/plug_parser.ex @@ -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. @@ -81,7 +81,7 @@ 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) @@ -89,7 +89,7 @@ if Code.ensure_compiled?(Plug) do 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"