Skip to content

Commit

Permalink
Add JSON matcher (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtrudel authored Dec 20, 2023
1 parent 0360907 commit 36289b4
Show file tree
Hide file tree
Showing 4 changed files with 88 additions and 1 deletion.
4 changes: 3 additions & 1 deletion lib/machete.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ defmodule Machete do
At its heart, Machete provides the following two things:
* A new `~>` operator (the 'squiggle arrow') that does flexible matching of
* A new `~>` operator (the 'squiggle arrow') that does flexible matching of
its left operator with its right operator
* A set of parametric matchers such as `string()` or `integer()` which can match
against general types
Expand Down Expand Up @@ -84,6 +84,7 @@ defmodule Machete do
* [`float()`](`Machete.FloatMatcher.float/1`) matches float values
* [`integer()`](`Machete.IntegerMatcher.integer/1`) matches integer values
* [`iso8601_datetime()`](`Machete.ISO8601DateTimeMatcher.iso8601_datetime/1`) matches ISO8601 formatted strings
* [`json()`](`Machete.JSONMatcher.json/1`) matches JSON formatted structures
* [`is_a()`](`Machete.IsAMatcher.is_a/1`) matches against a struct type
* [`naive_datetime()`](`Machete.NaiveDateTimeMatcher.naive_datetime/1`) matches `NaiveDateTime` instances
* [`pid()`](`Machete.PIDMatcher.pid/1`) matches process IDs
Expand Down Expand Up @@ -174,6 +175,7 @@ defmodule Machete do
import Machete.IntegerMatcher
import Machete.IsAMatcher
import Machete.ISO8601DateTimeMatcher
import Machete.JSONMatcher
import Machete.ListMatcher
import Machete.MapMatcher
import Machete.MaybeMatcher
Expand Down
61 changes: 61 additions & 0 deletions lib/machete/matchers/json_matcher.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
defmodule Machete.JSONMatcher do
@moduledoc """
Defines a matcher that matches JSON documents
"""

import Machete.Mismatch
import Machete.Operators

defstruct matcher: nil

@typedoc """
Describes an instance of this matcher
"""
@opaque t :: %__MODULE__{}

@doc """
Matches against JSON documents whose deserialization matches a provided matcher
Takes a matcher as its sole (mandatory) argument
Examples:
iex> assert "{}" ~> json(map())
true
iex> assert ~s({"a": 1}) ~> json(%{"a" => 1})
true
iex> assert "[]" ~> json(list())
true
iex> assert "[1,2,3]" ~> json([1,2,3])
true
iex> assert ~s("abc") ~> json(string())
true
iex> assert "123" ~> json(integer())
true
iex> assert "true" ~> json(boolean())
true
iex> assert "null" ~> json(nil)
true
"""
@spec json(term()) :: t()
def json(matcher), do: struct!(__MODULE__, matcher: matcher)

defimpl Machete.Matchable do
def mismatches(%@for{} = a, b) when is_binary(b) do
Jason.decode(b)
|> case do
{:ok, document} -> document ~>> a.matcher
_ -> mismatch("#{inspect(b)} is not parseable JSON")
end
end

def mismatches(%@for{}, b), do: mismatch("#{inspect(b)} is not a string")
end
end
2 changes: 2 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmodule Machete.MixProject do

defp deps do
[
{:jason, "~> 1.4"},
{:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
{:dialyxir, "~> 1.0", only: [:dev, :test], runtime: false}
Expand Down Expand Up @@ -58,6 +59,7 @@ defmodule Machete.MixProject do
Machete.IntegerMatcher,
Machete.IsAMatcher,
Machete.ISO8601DateTimeMatcher,
Machete.JSONMatcher,
Machete.ListMatcher,
Machete.MapMatcher,
Machete.MaybeMatcher,
Expand Down
22 changes: 22 additions & 0 deletions test/machete/matchers/json_matcher_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
defmodule JSONMatcherTest do
use ExUnit.Case, async: true
use Machete

import Machete.Mismatch

doctest Machete.JSONMatcher

test "produces a useful mismatch for non strings" do
assert 1 ~>> json(term()) ~> mismatch("1 is not a string")
end

test "produces a useful mismatch for non-parseable strings" do
assert "%" ~>> json(term()) ~> mismatch("\"%\" is not parseable JSON")
end

test "produces a useful mismatch for content mismatches" do
assert "[1]"
~>> json([])
~> mismatch("List is 1 elements in length, expected 0")
end
end

0 comments on commit 36289b4

Please sign in to comment.