Skip to content

Commit a8a8749

Browse files
committed
fix: resolve NewType subtype_of matching for filter args
1 parent 781e0ca commit a8a8749

File tree

6 files changed

+191
-9
lines changed

6 files changed

+191
-9
lines changed

lib/resource/resource.ex

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2745,13 +2745,22 @@ defmodule AshGraphql.Resource do
27452745
end
27462746

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

2759+
field_type_short_names =
2760+
Ash.Type.short_names()
2761+
|> Enum.filter(fn {_short_name, field_type} -> field_type in field_types end)
2762+
|> Enum.map(fn {short_name, _field_type} -> short_name end)
2763+
27552764
operator_types
27562765
|> Enum.filter(fn
27572766
[:any, {:array, type}] when is_atom(type) ->
@@ -2769,8 +2778,8 @@ defmodule AshGraphql.Resource do
27692778
[:any, type] when is_atom(type) ->
27702779
true
27712780

2772-
[^field_type_short_name, type] when is_atom(type) and not is_nil(field_type_short_name) ->
2773-
true
2781+
[expected_type, type] when is_atom(type) and is_atom(expected_type) ->
2782+
expected_type in field_type_short_names
27742783

27752784
_ ->
27762785
false
@@ -5027,8 +5036,8 @@ defmodule AshGraphql.Resource do
50275036
if Application.compile_env(:ash_graphql, :warn_on_json_fallback?, true) do
50285037
defp warn_on_json_fallback(resource, constraints) do
50295038
IO.warn("""
5030-
Struct type with instance_of constraint falls back to JsonString for input.
5031-
Consider creating a custom type with `use AshGraphql.Type` and `graphql_input_type/1`
5039+
Struct type with instance_of constraint falls back to JsonString for input.
5040+
Consider creating a custom type with `use AshGraphql.Type` and `graphql_input_type/1`
50325041
for structured validation.
50335042
50345043
Resource: #{inspect(resource)}

test/filter_sort_test.exs

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

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

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)