Skip to content

Commit 109f263

Browse files
committed
Add an extra pass of scanning unions to find maps, structs and typedstructs that are not referenced anywhere but in unions.
This cannot reasonbly be DRY'd up as the way the data is being treated post-initial-scan is different from how global_unions handle it.
1 parent 49c54d1 commit 109f263

File tree

5 files changed

+85
-26
lines changed

5 files changed

+85
-26
lines changed

lib/resource/resource.ex

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3227,8 +3227,9 @@ defmodule AshGraphql.Resource do
32273227

32283228
def map_definitions(resource, all_domains, schema, env) do
32293229
if AshGraphql.Resource.Info.type(resource) do
3230-
resource
3231-
|> global_maps(all_domains)
3230+
maps = global_maps(resource, all_domains)
3231+
3232+
maps
32323233
|> Enum.flat_map(fn attribute ->
32333234
type_name =
32343235
if function_exported?(attribute.type, :graphql_type, 1) do
@@ -3251,14 +3252,12 @@ defmodule AshGraphql.Resource do
32513252
[]
32523253
end
32533254

3254-
[
3255-
type_name
3256-
]
3257-
|> define_map_types(description, constraints, schema, resource, env, already_checked)
3258-
|> Enum.concat(
3259-
[
3260-
input_type_name
3261-
]
3255+
output_types =
3256+
[type_name]
3257+
|> define_map_types(description, constraints, schema, resource, env, already_checked)
3258+
3259+
input_types =
3260+
[input_type_name]
32623261
|> define_input_map_types(
32633262
description,
32643263
constraints,
@@ -3267,7 +3266,10 @@ defmodule AshGraphql.Resource do
32673266
env,
32683267
already_checked
32693268
)
3270-
)
3269+
3270+
all_types = output_types ++ input_types
3271+
3272+
all_types
32713273
end)
32723274
else
32733275
[]
@@ -3791,14 +3793,72 @@ defmodule AshGraphql.Resource do
37913793

37923794
@doc false
37933795
def global_maps(resource, all_domains) do
3794-
resource
3795-
|> AshGraphql.all_attributes_and_arguments(all_domains)
3796-
|> Enum.map(&unnest/1)
3797-
|> Enum.filter(
3798-
&(Ash.Type.NewType.subtype_of(&1.type) in [Ash.Type.Map, Ash.Type.Struct] &&
3799-
!Enum.empty?(Ash.Type.NewType.constraints(&1.type, &1.constraints)[:fields] || []) &&
3800-
define_type?(&1.type, &1.constraints))
3801-
)
3796+
# Scan direct resource attributes
3797+
direct_types =
3798+
resource
3799+
|> AshGraphql.all_attributes_and_arguments(all_domains)
3800+
|> Enum.map(&unnest/1)
3801+
|> Enum.filter(
3802+
&(Ash.Type.NewType.subtype_of(&1.type) in [Ash.Type.Map, Ash.Type.Struct] &&
3803+
!Enum.empty?(Ash.Type.NewType.constraints(&1.type, &1.constraints)[:fields] || []) &&
3804+
define_type?(&1.type, &1.constraints))
3805+
)
3806+
3807+
# Scan union member custom types
3808+
all_attributes = AshGraphql.all_attributes_and_arguments(resource, all_domains)
3809+
union_member_types = extract_union_member_custom_types(all_attributes)
3810+
3811+
# Combine and deduplicate
3812+
(direct_types ++ union_member_types)
3813+
|> Enum.uniq_by(&{&1.type, &1.constraints})
3814+
end
3815+
3816+
defp extract_union_member_custom_types(attributes) do
3817+
attributes
3818+
|> Enum.filter(&is_union_type?/1)
3819+
|> Enum.flat_map(&extract_custom_types_from_union/1)
3820+
end
3821+
3822+
defp is_union_type?(attribute) do
3823+
type = Ash.Type.get_type(attribute.type)
3824+
3825+
Ash.Type.NewType.new_type?(type) &&
3826+
Ash.Type.NewType.subtype_of(type) == Ash.Type.Union
3827+
end
3828+
3829+
defp extract_custom_types_from_union(union_attribute) do
3830+
constraints = Ash.Type.NewType.constraints(union_attribute.type, union_attribute.constraints)
3831+
3832+
constraints[:types]
3833+
|> Kernel.||([])
3834+
|> Enum.flat_map(fn {_name, config} ->
3835+
member_type = config[:type]
3836+
member_constraints = config[:constraints] || []
3837+
3838+
# Create fake attribute for the member type
3839+
fake_attribute = %{
3840+
union_attribute
3841+
| type: member_type,
3842+
constraints: member_constraints
3843+
}
3844+
3845+
# Check if it's a custom map/struct type that should generate definitions
3846+
if is_custom_map_or_struct_type?(fake_attribute) do
3847+
[fake_attribute]
3848+
else
3849+
[]
3850+
end
3851+
end)
3852+
end
3853+
3854+
defp is_custom_map_or_struct_type?(attribute) do
3855+
type = Ash.Type.get_type(attribute.type)
3856+
3857+
# Check if it's a Map/Struct type with fields AND implements AshGraphql.Type
3858+
Ash.Type.NewType.subtype_of(type) in [Ash.Type.Map, Ash.Type.Struct] &&
3859+
!Enum.empty?(Ash.Type.NewType.constraints(type, attribute.constraints)[:fields] || []) &&
3860+
function_exported?(type, :graphql_type, 1) &&
3861+
define_type?(type, attribute.constraints)
38023862
end
38033863

38043864
@spec define_type?(Ash.Type.t(), Ash.Type.constraints()) :: boolean()

test/support/types/person_map.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ defmodule AshGraphql.Test.PersonMap do
2727

2828
@impl true
2929
def graphql_input_type(_), do: :person_map_input_type
30-
end
30+
end

test/support/types/person_regular_struct.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,4 @@ defmodule AshGraphql.Test.PersonRegularStruct do
2727

2828
@impl true
2929
def graphql_input_type(_), do: :person_regular_input_type
30-
end
30+
end

test/support/types/person_typed_struct.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ defmodule AshGraphql.Test.PersonTypedStructData do
1616

1717
@impl true
1818
def graphql_input_type(_), do: :person_input_type
19-
end
19+
end

test/union_test.exs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ defmodule AshGraphql.UnionTest do
121121
assert data["__type"]["kind"] == "OBJECT"
122122

123123
# Should have a value field that references PersonType (wrapped in NonNull)
124-
value_field = Enum.find(data["__type"]["fields"], & &1["name"] == "value")
124+
value_field = Enum.find(data["__type"]["fields"], &(&1["name"] == "value"))
125125
assert value_field["type"]["kind"] == "NON_NULL"
126126
assert value_field["type"]["ofType"]["name"] == "PersonType"
127127
assert value_field["type"]["ofType"]["kind"] == "OBJECT"
@@ -151,7 +151,7 @@ defmodule AshGraphql.UnionTest do
151151

152152
# Should find the wrapper type for Map
153153
assert map_data["__type"]["name"] == "UniontypeMemberMap"
154-
map_value_field = Enum.find(map_data["__type"]["fields"], & &1["name"] == "value")
154+
map_value_field = Enum.find(map_data["__type"]["fields"], &(&1["name"] == "value"))
155155
assert map_value_field["type"]["kind"] == "NON_NULL"
156156
assert map_value_field["type"]["ofType"]["name"] == "PersonMapType"
157157
assert map_value_field["type"]["ofType"]["kind"] == "OBJECT"
@@ -181,7 +181,7 @@ defmodule AshGraphql.UnionTest do
181181

182182
# Should find the wrapper type for regular struct
183183
assert struct_data["__type"]["name"] == "UniontypeMemberRegularStruct"
184-
struct_value_field = Enum.find(struct_data["__type"]["fields"], & &1["name"] == "value")
184+
struct_value_field = Enum.find(struct_data["__type"]["fields"], &(&1["name"] == "value"))
185185
assert struct_value_field["type"]["kind"] == "NON_NULL"
186186
assert struct_value_field["type"]["ofType"]["name"] == "PersonRegularType"
187187
assert struct_value_field["type"]["ofType"]["kind"] == "OBJECT"
@@ -256,5 +256,4 @@ defmodule AshGraphql.UnionTest do
256256
assert input_data["__type"]["kind"] == "INPUT_OBJECT"
257257
end)
258258
end
259-
260259
end

0 commit comments

Comments
 (0)