Skip to content

Commit 501b269

Browse files
committed
fix: fully traverse nested map & union types
1 parent 7461346 commit 501b269

File tree

3 files changed

+191
-58
lines changed

3 files changed

+191
-58
lines changed

lib/ash_graphql.ex

Lines changed: 174 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,6 @@ defmodule AshGraphql do
467467
resource,
468468
all_domains,
469469
already_checked \\ [],
470-
nested? \\ true,
471470
return_new_checked? \\ false
472471
) do
473472
if resource in already_checked do
@@ -479,51 +478,186 @@ defmodule AshGraphql do
479478
else
480479
already_checked = [resource | already_checked]
481480

482-
resource
483-
|> Ash.Resource.Info.public_attributes()
484-
|> Enum.concat(all_arguments(resource, all_domains))
485-
|> Enum.concat(Ash.Resource.Info.calculations(resource))
486-
|> Enum.concat(
481+
attrs =
487482
resource
488-
|> Ash.Resource.Info.actions()
489-
|> Enum.filter(&(&1.type == :action && &1.returns))
490-
|> Enum.map(fn action ->
491-
%{
492-
type: action.returns,
493-
constraints: action.constraints,
494-
name: action.name,
495-
from_generic_action?: true
496-
}
483+
|> Ash.Resource.Info.public_attributes()
484+
|> Enum.concat(all_arguments(resource, all_domains))
485+
|> Enum.concat(Ash.Resource.Info.calculations(resource))
486+
|> Enum.concat(
487+
resource
488+
|> Ash.Resource.Info.actions()
489+
|> Enum.filter(&(&1.type == :action && &1.returns))
490+
|> Enum.map(fn action ->
491+
%{
492+
type: action.returns,
493+
constraints: action.constraints,
494+
name: action.name,
495+
from_generic_action?: true
496+
}
497+
end)
498+
)
499+
500+
{attrs, already_checked} =
501+
Enum.reduce(attrs, {[], already_checked}, fn
502+
%{type: type} = attr, {attrs, already_checked} ->
503+
constraints = Map.get(attr, :constraints, [])
504+
505+
{nested, already_checked} =
506+
nested_attrs(type, all_domains, constraints, already_checked)
507+
508+
nested =
509+
Enum.map(nested, &Map.put(&1, :original_name, attr.name))
510+
511+
{[attr | nested] ++ attrs, already_checked}
497512
end)
498-
)
499-
|> Enum.reduce({[], already_checked}, fn %{type: type} = attr, {acc, already_checked} ->
500-
if nested? do
501-
constraints = Map.get(attr, :constraints, [])
502513

503-
{nested, already_checked} =
504-
nested_attrs(type, all_domains, constraints, already_checked)
514+
{attrs, already_checked} =
515+
Enum.reduce(attrs, {[], already_checked}, fn attr, {attrs, already_checked} ->
516+
if attr.type in already_checked do
517+
{[attr | attrs], already_checked}
518+
else
519+
{new_attrs, already_checked} =
520+
expand_named_nested_attrs(
521+
Map.put(attr, :original_name, attr.name),
522+
already_checked
523+
)
524+
525+
{[attr | new_attrs ++ attrs], already_checked}
526+
end
527+
end)
528+
529+
attrs =
530+
Enum.filter(attrs, fn attr ->
531+
if Map.get(attr, :from_generic_action?) do
532+
true
533+
else
534+
AshGraphql.Resource.Info.show_field?(
535+
resource,
536+
Map.get(attr, :original_name, attr.name)
537+
)
538+
end
539+
end)
505540

506-
{[attr | nested] ++ acc, already_checked}
507-
else
508-
{[attr | acc], already_checked}
541+
if return_new_checked? do
542+
{attrs, already_checked}
543+
else
544+
attrs
545+
end
546+
end
547+
end
548+
549+
defp expand_named_nested_attrs(%{type: {:array, type}} = attr, already_checked) do
550+
expand_named_nested_attrs(
551+
%{attr | type: type, constraints: attr.constraints[:items] || []},
552+
already_checked
553+
)
554+
end
555+
556+
# sobelow_skip ["DOS.BinToAtom"]
557+
defp expand_named_nested_attrs(%{type: type} = attr, already_checked)
558+
when type in [Ash.Type.Map, Ash.Type.Struct, Ash.Type.Keyset] do
559+
Enum.reduce(
560+
attr.constraints[:fields] || [],
561+
{[], already_checked},
562+
fn {key, config}, {attrs, already_checked} ->
563+
case config[:type] do
564+
{:array, type} ->
565+
fake_attr = %{
566+
attr
567+
| name: :"#{attr.name}_#{key}",
568+
type: type,
569+
constraints: config[:constraints][:items] || []
570+
}
571+
572+
{new, already_checked} =
573+
expand_named_nested_attrs(
574+
fake_attr,
575+
already_checked
576+
)
577+
578+
{[fake_attr | attrs] ++ new, already_checked}
579+
580+
type ->
581+
fake_attr = %{
582+
attr
583+
| name: :"#{attr.name}_#{key}",
584+
type: type,
585+
constraints: config[:constraints] || []
586+
}
587+
588+
{new, already_checked} =
589+
expand_named_nested_attrs(
590+
fake_attr,
591+
already_checked
592+
)
593+
594+
{[fake_attr | attrs] ++ new, already_checked}
509595
end
510-
end)
511-
|> then(fn {attrs, checked} ->
512-
attrs =
513-
Enum.filter(attrs, fn attr ->
514-
if Map.get(attr, :from_generic_action?) do
515-
true
516-
else
517-
AshGraphql.Resource.Info.show_field?(resource, attr.name)
518-
end
519-
end)
596+
end
597+
)
598+
|> then(fn {attrs, already_checked} ->
599+
{[attr | attrs], already_checked}
600+
end)
601+
end
602+
603+
# sobelow_skip ["DOS.BinToAtom"]
604+
defp expand_named_nested_attrs(%{type: Ash.Type.Union} = attr, already_checked) do
605+
Enum.reduce(
606+
attr.constraints[:types] || [],
607+
{[], already_checked},
608+
fn {key, config}, {attrs, already_checked} ->
609+
case config[:type] do
610+
{:array, type} ->
611+
fake_attr = %{
612+
attr
613+
| name: :"#{attr.name}_#{key}",
614+
type: type,
615+
constraints: config[:constraints][:items] || []
616+
}
520617

521-
if return_new_checked? do
522-
{attrs, checked}
523-
else
524-
attrs
618+
{new, already_checked} =
619+
expand_named_nested_attrs(
620+
fake_attr,
621+
already_checked
622+
)
623+
624+
{[fake_attr | attrs] ++ new, already_checked}
625+
626+
type ->
627+
fake_attr = %{
628+
attr
629+
| name: :"#{attr.name}_#{key}",
630+
type: type,
631+
constraints: config[:constraints] || []
632+
}
633+
634+
{new, already_checked} =
635+
expand_named_nested_attrs(
636+
fake_attr,
637+
already_checked
638+
)
639+
640+
{[fake_attr | attrs] ++ new, already_checked}
525641
end
526-
end)
642+
end
643+
)
644+
|> then(fn {attrs, already_checked} ->
645+
{[attr | attrs], already_checked}
646+
end)
647+
end
648+
649+
defp expand_named_nested_attrs(attr, already_checked) do
650+
if Ash.Type.NewType.new_type?(attr.type) and attr.type not in already_checked do
651+
constraints = Ash.Type.NewType.constraints(attr.type, attr.constraints)
652+
653+
already_checked = [attr.type | already_checked]
654+
655+
expand_named_nested_attrs(
656+
%{attr | type: Ash.Type.NewType.subtype_of(attr.type), constraints: constraints},
657+
already_checked
658+
)
659+
else
660+
{[], already_checked}
527661
end
528662
end
529663

@@ -604,7 +738,7 @@ defmodule AshGraphql do
604738
AshGraphql.Resource.embedded?(type) ->
605739
type
606740
|> unwrap_type()
607-
|> all_attributes_and_arguments(all_domains, already_checked, true, true)
741+
|> all_attributes_and_arguments(all_domains, already_checked, true)
608742

609743
Ash.Type.NewType.new_type?(type) ->
610744
constraints = Ash.Type.NewType.constraints(type, constraints)

lib/resource/resource.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3710,7 +3710,7 @@ defmodule AshGraphql.Resource do
37103710
@doc false
37113711
def global_maps(resource, all_domains) do
37123712
resource
3713-
|> AshGraphql.all_attributes_and_arguments(all_domains, [], false)
3713+
|> AshGraphql.all_attributes_and_arguments(all_domains)
37143714
|> Enum.map(&unnest/1)
37153715
|> Enum.filter(
37163716
&(Ash.Type.NewType.subtype_of(&1.type) in [Ash.Type.Map, Ash.Type.Struct] &&

test/resource_test.exs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -145,21 +145,20 @@ defmodule AshGraphql.ResourceTest do
145145
end
146146

147147
test "can create resource with type inside type" do
148-
{:ok, %{data: %{"createTypeInsideType" => true}}} =
149-
"""
150-
mutation {
151-
unreferencedTypeInsideTypeWorks(input:{
152-
typeWithType: {
153-
inner_type: {
154-
some: "foo",
155-
stuff: "bar"
156-
},
157-
another_field: "baz"
158-
}
159-
})
160-
}
161-
"""
162-
|> Absinthe.run(AshGraphql.Test.Schema)
163-
164-
end
148+
assert {:ok, %{data: %{"createTypeInsideType" => true}}} =
149+
"""
150+
mutation {
151+
createTypeInsideType(input:{
152+
typeWithType: {
153+
inner_type: {
154+
some: "foo",
155+
stuff: "bar"
156+
},
157+
another_field: "baz"
158+
}
159+
})
160+
}
161+
"""
162+
|> Absinthe.run(AshGraphql.Test.Schema)
163+
end
165164
end

0 commit comments

Comments
 (0)