lib/utils/maps.ex

defmodule Matcher.Utils.Maps do
  import Matcher.Errors

  def compare_maps(expected, actual, context, allow_unexpected?: allow_unexpected?)
      when is_map(expected) and is_map(actual) do
    expected_results =
      Enum.map(expected, fn {key, matcher} ->
        find_value(key, matcher, actual)
      end)

    unexpected = Map.keys(actual) -- Map.keys(expected)

    if Enum.all?(expected_results, fn {_, _, _, match} ->
         Matcher.Utils.matched?(match)
       end) and (Enum.empty?(unexpected) or allow_unexpected?) do
      {:ok, nil}
    else
      {:error, error(context, [])}
    end
  end

  def compare_maps(e, a, context, _) do
    error(context, message: "expected #{expected(e)}, but got #{mismatched(a)}")
  end

  defp find_value(key, matcher, actual_map) do
    actual_value = Map.get(actual_map, key)
    match = Matcher.match(matcher, actual_value)
    {key, matcher, actual_value, match}
  end
end