Skip to content

Commit

Permalink
Add basic support for ^strict_map() operator
Browse files Browse the repository at this point in the history
  • Loading branch information
zoldar committed Jan 29, 2025
1 parent 1201d8c commit ef3c33d
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -355,15 +355,15 @@ defmodule PlausibleWeb.Api.Internal.SegmentsControllerTest do
})
|> json_response(200)

assert_matches %{
assert_matches ^strict_map(%{
"id" => ^any(:integer),
"name" => "Some segment",
"type" => ^"#{unquote(type)}",
"segment_data" => %{"filters" => [["is", "visit:entry_page", ["/blog"]]]},
"owner_id" => ^user.id,
"inserted_at" => ^any(:string),
"updated_at" => ^any(:string)
} = response
}) = response

assert response["inserted_at"] == response["updated_at"]

Expand Down
113 changes: 101 additions & 12 deletions test/support/assert_matches.ex
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,70 @@ defmodule Plausible.AssertMatches do
"""

defmacro assert_matches({:=, meta, [pattern, value]}) do
{strict_pattern, strict_pins} =
Macro.postwalk(pattern, [], fn
{:^, _meta, [{:strict_map, _, [pinned]}]}, acc ->
pinned_clean =
Macro.postwalk(pinned, fn
{:^, _, _} ->
{:_, [], __MODULE__}

other ->
other
end)

pinned_var =
Macro.unique_var(:match, __MODULE__)
|> Macro.update_meta(&Keyword.put(&1, :strict_match, true))

{pinned_var, [{pinned_var, pinned_clean} | acc]}

{:^, _, _}, acc ->
{{:_, [], __MODULE__}, acc}

other, acc ->
{other, acc}
end)

strict_checked_pattern =
quote bind_quoted: [
strict_pattern: Macro.escape(strict_pattern),
escaped_pins: Macro.escape(strict_pins),
pins: Enum.map(strict_pins, &elem(&1, 0))
] do
escaped_pins
|> Enum.zip(pins)
|> Enum.reduce({false, strict_pattern}, fn {{escaped_var, escaped_map}, var},
{errors?, pattern} ->
{:%{}, _, map_pattern_values} = escaped_map
map_pattern_keys = map_pattern_values |> Enum.map(&elem(&1, 0)) |> Enum.sort()
var_keys = var |> Map.keys() |> Enum.sort()

if map_pattern_keys != var_keys do
{true,
Macro.postwalk(pattern, fn
^escaped_var -> {:strict_map, [], [escaped_map]}
other -> other
end)}
else
{errors?,
Macro.postwalk(pattern, fn
^escaped_var -> escaped_map
other -> other
end)}
end
end)
end

{var_pattern, pins} =
Macro.postwalk(pattern, [], fn
{:^, _meta, {pinned, _, module}} = normal_pin, acc
{:^, _meta, [{pinned, _, module}]} = normal_pin, acc
when is_atom(pinned) and is_atom(module) ->
{normal_pin, acc}

{:^, _meta, [{:strict_map, _, [pinned]}]}, acc ->
{pinned, acc}

{:^, _meta, [pinned]}, acc ->
pinned = Plausible.AssertMatches.Internal.transform_predicate(pinned)

Expand All @@ -89,6 +147,17 @@ defmodule Plausible.AssertMatches do

var_pattern =
Macro.postwalk(var_pattern, fn
{:^, _, [{name, meta, module}]} = pin when is_atom(name) and is_atom(module) ->
if meta[:assert_match] do
pin
else
{:_, [], __MODULE__}
end

other ->
other
end)
|> Macro.postwalk(fn
{name, meta, module} = var when is_atom(name) and is_atom(module) ->
if meta[:assert_match] do
var
Expand Down Expand Up @@ -139,18 +208,38 @@ defmodule Plausible.AssertMatches do
quote do
value = unquote(value)
assert unquote(clean_pattern) = value
assert unquote(strict_pattern) = value

if unquote(length(strict_pins)) > 0 do
{strict_errors?, strict_pattern} = unquote(strict_checked_pattern)

if strict_errors? do
raise ExUnit.AssertionError,
message: "match (=) failed",
left: strict_pattern,
right: value,
expr:
{:assert_matches, unquote(meta),
[{:=, [], [unquote(Macro.escape(pattern)), Macro.escape(value)]}]},
context: {:match, []}
end
end

assert unquote(var_pattern) = value
{errors?, predicate_pattern} = unquote(predicate_pattern)

if errors? do
raise ExUnit.AssertionError,
message: "match (=) failed",
left: predicate_pattern,
right: value,
expr:
{:assert_matches, unquote(meta),
[{:=, [], [unquote(Macro.escape(pattern)), Macro.escape(value)]}]},
context: {:match, []}

if unquote(length(pins) > 0) do
{errors?, predicate_pattern} = unquote(predicate_pattern)

if errors? do
raise ExUnit.AssertionError,
message: "match (=) failed",
left: predicate_pattern,
right: value,
expr:
{:assert_matches, unquote(meta),
[{:=, [], [unquote(Macro.escape(pattern)), Macro.escape(value)]}]},
context: {:match, []}
end
end
end
end
Expand Down

0 comments on commit ef3c33d

Please sign in to comment.