jibe v0.2.4 Jibe View Source
Tools for checking if an arbitrarily nested map/list matches a particular pattern. The genesis of this was as a helper in unit tests to check if JSON was being generated as expected.
This does not require any particular JSON library, so if you want to use it with
JSON you'll need to decode it with something like Poison.decode/1
first. Note that these
decode functions will usually build maps with strings for keys, as opposed to atoms, so set
up your pattern accordingly.
Link to this section Summary
Functions
Given a pattern
and an actual
map/list, determine if the pattern is a subset of the
actual.
Link to this section Functions
Given a pattern
and an actual
map/list, determine if the pattern is a subset of the
actual.
The pattern is "forgiving" in the sense that extra keys or list elements in the actual are OK. However, every key and/or element in the pattern must appear in the actual. Anything missing in the actual is a failure.
List order is significant by default.
Examples
# Simple equality tests.
iex> Jibe.match?(%{}, %{})
true
iex> Jibe.match?([], [])
true
# Hash without nested values. Order of keys is not important,
# since hash keys aren't ordered in the first place.
iex> Jibe.match?(%{"foo" => "bar", "a" => 123}, %{"a" => 123, "foo" => "bar"})
true
# The keys are good, but the values don't match.
iex> Jibe.match?(%{foo: :bar}, %{foo: :not_bar})
false
# Fails because not all expected hash elements are found.
iex> Jibe.match?(%{"foo" => "bar", "a" => 123}, %{"a" => 123})
false
# Also works in nested maps. (`y: 2` is missing from the `actual`)
iex> Jibe.match?(%{foo: :bar, a: %{x: 1, y: 2}}, %{a: %{x: 1}, foo: :bar})
false
# Extra keys in the actual don't matter.
iex> Jibe.match?(%{"foo" => "bar"}, %{"a" => 123, "foo" => "bar"})
true
iex> Jibe.match?(%{foo: :bar, a: %{x: 1}}, %{a: %{x: 1, y: 2}, foo: :bar})
true
# Simple list comparison.
iex> Jibe.match?([1,2,3], [1,2,3])
true
# Extra list elements in the actual are OK.
iex> Jibe.match?([1,2,3], [1,2,3,4])
true
iex> Jibe.match?([1,2, [3,4]], [1,2, [3,4,5]])
true
# The position of extra list elements does not matter.
iex> Jibe.match?([2,4], [1,2,3,4,5])
true
# Missing list elements in the actual are a failure.
iex> Jibe.match?([1,2,3], [1,2])
false
iex> Jibe.match?([1,2, [3,4]], [1,2, [3]])
false
# Mix and matching maps and lists.
iex> Jibe.match?([1, %{foo: :bar}], [0, 1, 2, %{foo: :bar}, 3])
true
# Nested data where both sides are the same.
iex> pattern = [%{"a" => 1, "b" => [2, 3, %{"x" => [4,5]}]}, 9, 10]
iex> actual = [%{"a" => 1, "b" => [2, 3, %{"x" => [4,5]}]}, 9, 10]
iex> Jibe.match?(pattern, actual)
true
# Nested data, actual is missing a list element.
iex> pattern = [%{"a" => 1, "b" => [2, 3, %{"x" => [4,5]}]}, 9, 10]
iex> actual = [%{"a" => 1, "b" => [2, 3, %{"x" => [4]}]}, 9, 10]
iex> Jibe.match?(pattern, actual)
false
# Nested data, actual has an extra list element, which is fine.
iex> pattern = [%{"a" => 1, "b" => [2, 3, %{"x" => [4]}]}, 9, 10]
iex> actual = [%{"a" => 1, "b" => [2, 3, %{"x" => [4,5]}]}, 9, 10]
iex> Jibe.match?(pattern, actual)
true
# Nested data, actual map inside of a list has an extra element.
iex> pattern = [%{"a" => 1}, %{"a" => 2}]
iex> actual = [%{"a" => 1, "x" => 9}, %{"y" => 9, "a" => 2}]
iex> Jibe.match?(pattern, actual)
true
# Finding the matching map within a list of maps
iex> pattern = [%{foo: :bar}]
iex> actual = [%{foo: :x}, %{foo: :bar}, %{foo: :y}]
iex> Jibe.match?(pattern, actual)
true
Tuples
They're converted to lists insternally, so the same rules apply.
iex> pattern = {1, 2}
iex> actual = {1, 2}
iex> Jibe.match?(pattern, actual)
true
iex> pattern = {1, 2}
iex> actual = {1, 2, 3}
iex> Jibe.match?(pattern, actual)
true
iex> pattern = {1, 2}
iex> actual = {1}
iex> Jibe.match?(pattern, actual)
false
iex> pattern = [{1, 2}, {3, 4}]
iex> actual = [{1, 2, 3}, {3, 5, 4}]
iex> Jibe.match?(pattern, actual)
true
Unsorted lists
This is a performance hit on your tests. Avoid large lists if you're especially worried about speed.
iex> Jibe.match?({:unsorted, [1, 2]}, [2, 1])
true
Wildcard matches
# "Something" needs to be there, but we don't care what it is.
iex> Jibe.match?([1, :wildcard, 3], [1, 999, 3])
true
# :wildcard still needs the key to be present in a map.
iex> Jibe.match?(%{"foo" => :wildcard}, %{"x" => "bar"})
false
# :wildcard will match a nil value as long as the key is there.
iex> Jibe.match?(%{"foo" => :wildcard}, %{"foo" => nil})
true
DateTime matching
iex> {:ok, d1, _} = DateTime.from_iso8601("2018-01-01T12:00:00Z")
iex> {:ok, d2, _} = DateTime.from_iso8601("2018-01-01T12:00:00.000000Z")
iex> Jibe.match?([d1], [d2])
true
iex> {:ok, d1, _} = DateTime.from_iso8601("2018-01-01T12:00:00Z")
iex> {:ok, d2, _} = DateTime.from_iso8601("2018-01-01T12:00:00.000000Z")
iex> Jibe.match?(%{d: d1}, %{d: d2})
true
iex> {:ok, d1, _} = DateTime.from_iso8601("2018-01-01T12:00:00Z")
iex> {:ok, d2, _} = DateTime.from_iso8601("2000-01-01T12:00:00Z")
iex> Jibe.match?([d1], [d2])
false
Decimal matching
# Decimal values require a special comparison
iex> Jibe.match?([Decimal.new(2)], [Decimal.from_float(2.0)])
true
iex> Jibe.match?([Decimal.new(4)], [Decimal.from_float(4.5)])
false