Skip to content

Commit e35d396

Browse files
authored
improvement: filter expression verifier (#2243)
* Add verifier for filter expressions * Test verifier for filter expressions
1 parent 1174f2a commit e35d396

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

lib/ash/resource/dsl.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1631,6 +1631,7 @@ defmodule Ash.Resource.Dsl do
16311631
Ash.Resource.Verifiers.VerifyIdentityFields,
16321632
Ash.Resource.Verifiers.VerifyPrimaryReadActionHasNoArguments,
16331633
Ash.Resource.Verifiers.VerifySelectedByDefault,
1634+
Ash.Resource.Verifiers.VerifyFilterExpressions,
16341635
Ash.Resource.Verifiers.EnsureAggregateFieldIsAttributeOrCalculation,
16351636
Ash.Resource.Verifiers.ValidateRelationshipAttributes,
16361637
Ash.Resource.Verifiers.NoReservedFieldNames,
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
defmodule Ash.Resource.Verifiers.VerifyFilterExpressions do
2+
@moduledoc """
3+
Raises an error if a filter expression references an undefined argument.
4+
"""
5+
use Spark.Dsl.Verifier
6+
7+
alias Spark.Error.DslError
8+
9+
@impl true
10+
def verify(dsl) do
11+
module = Spark.Dsl.Verifier.get_persisted(dsl, :module)
12+
13+
dsl
14+
|> Ash.Resource.Info.actions()
15+
|> Enum.each(fn action ->
16+
verify_action_filter!(module, action)
17+
end)
18+
19+
:ok
20+
end
21+
22+
defp verify_action_filter!(module, %{filter: filter} = action) when not is_nil(filter) do
23+
argument_keys = action.arguments |> Enum.map(& &1.name) |> MapSet.new()
24+
25+
Ash.Expr.walk_template(filter, fn
26+
{:_arg, field} = expr ->
27+
field = if is_binary(field), do: String.to_atom(field), else: field
28+
29+
if MapSet.member?(argument_keys, field) do
30+
expr
31+
else
32+
raise DslError,
33+
module: module,
34+
message:
35+
"Filter expression references undefined argument `#{field}`. Available arguments: #{inspect(MapSet.to_list(argument_keys))}",
36+
path: [:actions, action.name, :filter]
37+
end
38+
39+
other ->
40+
other
41+
end)
42+
end
43+
44+
defp verify_action_filter!(_module, _action), do: :ok
45+
end
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
defmodule Ash.Test.Resource.VerifyFilterExpressionsTest do
2+
@moduledoc false
3+
use ExUnit.Case, async: true
4+
5+
import Ash.Test.Helpers
6+
alias Spark.Error.DslError
7+
8+
test "Filter expression with undefined argument raises an error" do
9+
assert_raise DslError,
10+
~r/Filter expression references undefined argument `undefined_arg`/,
11+
fn ->
12+
defposts do
13+
actions do
14+
read :example_action do
15+
argument :valid_arg, :string
16+
filter expr(some_field == ^arg(:undefined_arg))
17+
end
18+
end
19+
end
20+
end
21+
end
22+
23+
test "Filter expression with valid argument does not raise an error" do
24+
post =
25+
defposts do
26+
actions do
27+
read :example_action do
28+
argument :valid_arg, :string
29+
filter expr(some_field == ^arg(:valid_arg))
30+
end
31+
end
32+
end
33+
34+
action = Ash.Resource.Info.action(post, :example_action)
35+
assert action.name == :example_action
36+
end
37+
38+
test "Filter expression with valid string argument does not raise an error" do
39+
post =
40+
defposts do
41+
actions do
42+
read :example_action do
43+
argument :valid_arg, :string
44+
filter expr(some_field == ^arg("valid_arg"))
45+
end
46+
end
47+
end
48+
49+
action = Ash.Resource.Info.action(post, :example_action)
50+
assert action.name == :example_action
51+
end
52+
end

0 commit comments

Comments
 (0)