Skip to content

Commit ded9fab

Browse files
authored
fix: resolve NewType subtype_of matching for filter args (#412)
1 parent 15d4036 commit ded9fab

File tree

6 files changed

+189
-7
lines changed

6 files changed

+189
-7
lines changed

lib/resource/resource.ex

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2749,13 +2749,22 @@ defmodule AshGraphql.Resource do
27492749
end
27502750

27512751
defp do_get_expressable_types(operator_types, field_type, array_type?) do
2752-
field_type_short_name =
2753-
case Ash.Type.short_names()
2754-
|> Enum.find(fn {_, type} -> type == field_type end) do
2755-
nil -> nil
2756-
{short_name, _} -> short_name
2752+
# For NewType subtypes, also match against the base type's short name
2753+
field_types =
2754+
case Ash.Type.NewType.new_type?(field_type) do
2755+
true ->
2756+
base_type = Ash.Type.NewType.subtype_of(field_type)
2757+
[base_type, field_type]
2758+
2759+
false ->
2760+
[field_type]
27572761
end
27582762

2763+
field_type_short_names =
2764+
Ash.Type.short_names()
2765+
|> Enum.filter(fn {_short_name, field_type} -> field_type in field_types end)
2766+
|> Enum.map(fn {short_name, _field_type} -> short_name end)
2767+
27592768
operator_types
27602769
|> Enum.filter(fn
27612770
[:any, {:array, type}] when is_atom(type) ->
@@ -2773,8 +2782,8 @@ defmodule AshGraphql.Resource do
27732782
[:any, type] when is_atom(type) ->
27742783
true
27752784

2776-
[^field_type_short_name, type] when is_atom(type) and not is_nil(field_type_short_name) ->
2777-
true
2785+
[expected_type, type] when is_atom(type) and is_atom(expected_type) ->
2786+
expected_type in field_type_short_names
27782787

27792788
_ ->
27802789
false

test/filter_sort_test.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,29 @@ defmodule AshGraphql.FilterSortTest do
111111
assert "contains" in field_names
112112
end
113113

114+
test "filterable_fields resolves NewType subtype_of for data layer function operator matching" do
115+
resp =
116+
"""
117+
query {
118+
__type(name: "TagWithIlikeFilterLabel") {
119+
inputFields {
120+
name
121+
}
122+
}
123+
}
124+
125+
"""
126+
|> Absinthe.run(AshGraphql.Test.Schema)
127+
128+
assert {:ok, %{data: %{"__type" => %{"inputFields" => input_fields}}}} = resp
129+
130+
field_names = Enum.map(input_fields, & &1["name"])
131+
assert "eq" in field_names
132+
assert "ilike" in field_names
133+
refute "lessThan" in field_names
134+
refute "in" in field_names
135+
end
136+
114137
test "sortable_fields option is applied" do
115138
resp =
116139
"""

test/support/domain.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,6 @@ defmodule AshGraphql.Test.Domain do
7272
resource(AshGraphql.Test.ResourceWithTypedStruct)
7373
resource(AshGraphql.Test.AfterTransactionEts)
7474
resource(AshGraphql.Test.AfterTransactionMnesia)
75+
resource(AshGraphql.Test.TagWithIlike)
7576
end
7677
end

test/support/ets_with_functions.ex

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# SPDX-FileCopyrightText: 2020 ash_graphql contributors <https://github.com/ash-project/ash_graphql/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule AshGraphql.Test.DataLayer.EtsWithFunctions do
6+
@moduledoc false
7+
@behaviour Ash.DataLayer
8+
9+
@ets_with_functions %Spark.Dsl.Section{
10+
name: :ets_with_functions,
11+
describe: "ETS data layer with custom functions for testing",
12+
schema: [
13+
private?: [
14+
type: :boolean,
15+
default: false
16+
],
17+
table: [
18+
type: :atom
19+
],
20+
repo: [
21+
type: :atom,
22+
doc: "A fake repo option to simulate AshPostgres pattern"
23+
]
24+
]
25+
}
26+
27+
use Spark.Dsl.Extension,
28+
sections: [@ets_with_functions]
29+
30+
@impl true
31+
defdelegate can?(resource, feature), to: Ash.DataLayer.Ets
32+
33+
@impl true
34+
defdelegate resource_to_query(resource, domain), to: Ash.DataLayer.Ets
35+
36+
@impl true
37+
defdelegate limit(query, limit, resource), to: Ash.DataLayer.Ets
38+
39+
@impl true
40+
defdelegate offset(query, offset, resource), to: Ash.DataLayer.Ets
41+
42+
@impl true
43+
defdelegate add_calculations(query, calculations, resource), to: Ash.DataLayer.Ets
44+
45+
@impl true
46+
defdelegate add_aggregate(query, aggregate, resource), to: Ash.DataLayer.Ets
47+
48+
@impl true
49+
defdelegate set_tenant(resource, query, tenant), to: Ash.DataLayer.Ets
50+
51+
@impl true
52+
defdelegate set_context(resource, query, context), to: Ash.DataLayer.Ets
53+
54+
@impl true
55+
defdelegate select(query, select, resource), to: Ash.DataLayer.Ets
56+
57+
@impl true
58+
defdelegate filter(query, filter, resource), to: Ash.DataLayer.Ets
59+
60+
@impl true
61+
defdelegate sort(query, sort, resource), to: Ash.DataLayer.Ets
62+
63+
@impl true
64+
defdelegate distinct(query, distinct, resource), to: Ash.DataLayer.Ets
65+
66+
@impl true
67+
defdelegate distinct_sort(query, distinct_sort, resource), to: Ash.DataLayer.Ets
68+
69+
@impl true
70+
defdelegate run_query(query, resource), to: Ash.DataLayer.Ets
71+
72+
@impl true
73+
defdelegate run_aggregate_query(query, aggregates, resource), to: Ash.DataLayer.Ets
74+
75+
@impl true
76+
defdelegate create(resource, changeset), to: Ash.DataLayer.Ets
77+
78+
@impl true
79+
defdelegate destroy(resource, changeset), to: Ash.DataLayer.Ets
80+
81+
@impl true
82+
defdelegate update(resource, changeset), to: Ash.DataLayer.Ets
83+
84+
@impl true
85+
defdelegate combination_of(combinations, resource, domain), to: Ash.DataLayer.Ets
86+
87+
@impl true
88+
defdelegate prefer_lateral_join_for_many_to_many?, to: Ash.DataLayer.Ets
89+
90+
@impl true
91+
defdelegate calculate(resource, expressions, context), to: Ash.DataLayer.Ets
92+
93+
@impl true
94+
def functions(resource) do
95+
# Simulate AshPostgres pattern: access a DSL option, then call a method on it
96+
# AshPostgres does: AshPostgres.DataLayer.Info.repo(resource, :mutate).config()
97+
repo = Spark.Dsl.Extension.get_opt(resource, [:ets_with_functions], :repo, nil, true)
98+
99+
if repo do
100+
_config = repo.config()
101+
end
102+
103+
[AshGraphql.Test.Functions.TestILike]
104+
end
105+
end
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# SPDX-FileCopyrightText: 2020 ash_graphql contributors <https://github.com/ash-project/ash_graphql/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule AshGraphql.Test.TagWithIlike do
6+
@moduledoc false
7+
8+
use Ash.Resource,
9+
domain: AshGraphql.Test.Domain,
10+
data_layer: AshGraphql.Test.DataLayer.EtsWithFunctions,
11+
extensions: [AshGraphql.Resource]
12+
13+
graphql do
14+
type(:tag_with_ilike)
15+
16+
filterable_fields [:name, label: [:eq, :ilike]]
17+
18+
queries do
19+
list :get_tags_with_ilike, :read
20+
end
21+
end
22+
23+
actions do
24+
default_accept(:*)
25+
defaults([:read, :create])
26+
end
27+
28+
attributes do
29+
uuid_primary_key(:id)
30+
31+
attribute(:name, :string, public?: true)
32+
attribute(:label, AshGraphql.Types.StringNewType, public?: true)
33+
end
34+
end
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SPDX-FileCopyrightText: 2020 ash_graphql contributors <https://github.com/ash-project/ash_graphql/graphs/contributors>
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
defmodule AshGraphql.Test.Functions.TestILike do
6+
@moduledoc false
7+
use Ash.Query.Function, name: :ilike, predicate?: true
8+
9+
def args, do: [[:string, :string]]
10+
end

0 commit comments

Comments
 (0)