lib/macfly/map_fix.ex

defprotocol Macfly.MapFix do
  @fallback_to_any true
  @spec traverse(t()) :: t() | Msgpax.Fragment
  def traverse(t)
end

defimpl Macfly.MapFix, for: Map do
  def traverse(t) do
    Msgpax.Fragment.new(pack(t))
  end

  # https://github.com/lexmag/msgpax/blob/e2f31aacdbd476fec4be6abbc88d51e0dfae8c9a/lib/msgpax/packer.ex#L160-L185
  def pack(map) do
    [format(map) | map |> Map.to_list() |> Enum.sort_by(& &1) |> pack_elts()]
  end

  defp pack_elts([]), do: []

  defp pack_elts([{key, value} | rest]) do
    key =
      key
      |> @protocol.traverse()
      |> Msgpax.Packer.pack()

    value =
      value
      |> @protocol.traverse()
      |> Msgpax.Packer.pack()

    [key, value | pack_elts(rest)]
  end

  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})
    end
  end
end

defimpl Macfly.MapFix, for: Any do
  def traverse(t) do
    if Enumerable.impl_for(t) do
      Enum.map(t, &@protocol.traverse(&1))
    else
      t
    end
  end
end