diff --git a/lib/graphql/resolver.ex b/lib/graphql/resolver.ex index 671694d..0df70df 100644 --- a/lib/graphql/resolver.ex +++ b/lib/graphql/resolver.ex @@ -314,9 +314,9 @@ defmodule AshGraphql.Graphql.Resolver do Enum.find(action_arguments, &(&1.name == key)) || Enum.find(attributes, &(&1.name == key)) if argument do - %{type: type, name: name} = argument + %{type: type, name: name, constraints: constraints} = argument - case handle_argument(type, value, name) do + case handle_argument(type, constraints, value, name) do {:ok, value} -> {:cont, {:ok, Map.put(arguments, name, value)}} @@ -329,15 +329,81 @@ defmodule AshGraphql.Graphql.Resolver do end) end - defp handle_argument(type, value, name) do - if union_type?(type) do - handle_union_type(value, name) - else - {:ok, value} + defp handle_argument({:array, type}, constraints, value, name) when is_list(value) do + value + |> Enum.reduce_while({:ok, []}, fn value, {:ok, acc} -> + case handle_argument(type, constraints[:items], value, name) do + {:ok, value} -> + {:cont, {:ok, [value | acc]}} + + {:error, error} -> + {:halt, {:error, error}} + end + end) + |> case do + {:ok, value} -> {:ok, Enum.reverse(value)} + {:error, error} -> {:error, error} end end - defp handle_union_type(value, name) do + defp handle_argument(Ash.Type.Union, constraints, value, name) do + handle_union_type(value, constraints, name) + end + + defp handle_argument(type, constraints, value, name) do + if Ash.Type.embedded_type?(type) and is_map(value) do + create_action = + if constraints[:create_action] do + Ash.Resource.Info.action(type, constraints[:create_action]) || + Ash.Resource.Info.primary_action(type, :create) + else + Ash.Resource.Info.primary_action(type, :create) + end + + update_action = + if constraints[:update_action] do + Ash.Resource.Info.action(type, constraints[:update_action]) || + Ash.Resource.Info.primary_action(type, :update) + else + Ash.Resource.Info.primary_action(type, :update) + end + + attributes = Ash.Resource.Info.public_attributes(type) + + fields = + cond do + create_action && update_action -> + create_action.arguments ++ update_action.arguments ++ attributes + + update_action -> + update_action.arguments ++ attributes + + create_action -> + create_action.arguments ++ attributes + + true -> + attributes + end + + {:ok, + Map.new(value, fn {key, value} -> + field = + Enum.find(fields, fn field -> + field.name == key + end) + + if field do + {key, handle_argument(field.type, field.constraints, value, "#{name}.#{key}")} + else + {key, value} + end + end)} + end + + {:ok, value} + end + + defp handle_union_type(value, constraints, name) do value |> Enum.reject(fn {_key, value} -> is_nil(value) @@ -346,8 +412,14 @@ defmodule AshGraphql.Graphql.Resolver do [] -> {:ok, nil} - [{_key, value}] -> - {:ok, value} + [{key, value}] -> + config = constraints[:types][key] + + if config[:tag] && is_map(value) do + {:ok, Map.put_new(value, config[:tag], config[:tag_value])} + else + {:ok, value} + end key_vals -> keys = Enum.map_join(key_vals, ", ", fn {key, _} -> to_string(key) end) @@ -357,10 +429,6 @@ defmodule AshGraphql.Graphql.Resolver do end end - defp union_type?({:array, type}), do: union_type?(type) - defp union_type?(Ash.Type.Union), do: true - defp union_type?(_), do: false - def validate_resolve_opts(resolution, pagination, opts, args) do with page_opts <- args diff --git a/lib/resource/resource.ex b/lib/resource/resource.ex index f8af633..98b30fb 100644 --- a/lib/resource/resource.ex +++ b/lib/resource/resource.ex @@ -3100,7 +3100,7 @@ defmodule AshGraphql.Resource do AshGraphql.Resource.Info.type(type) else if Ash.Type.embedded_type?(type) do - if input? do + if input? && type(type) do :"#{AshGraphql.Resource.Info.type(resource)}_#{attribute.name}_input" else case type(type) do diff --git a/test/create_test.exs b/test/create_test.exs index d249a40..0ce810c 100644 --- a/test/create_test.exs +++ b/test/create_test.exs @@ -146,6 +146,64 @@ defmodule AshGraphql.CreateTest do } = result end + test "an embedded union type can be written to" do + resp = + """ + mutation SimpleCreatePost($input: SimpleCreatePostInput) { + simpleCreatePost(input: $input) { + result{ + text1 + embedUnion { + ... on PostEmbedUnionFoo { + value { + foo + } + } + ... on PostEmbedUnionBar { + value { + bar + } + } + } + } + errors{ + message + } + } + } + """ + |> Absinthe.run(AshGraphql.Test.Schema, + variables: %{ + "input" => %{ + "text1" => "foo", + "embedUnion" => %{ + "foo" => %{ + "foo" => "10" + } + } + } + } + ) + + assert {:ok, result} = resp + + refute Map.has_key?(result, :errors) + + assert %{ + data: %{ + "simpleCreatePost" => %{ + "result" => %{ + "embedUnion" => %{ + "value" => %{ + "foo" => "10" + } + } + } + } + } + } = result + end + test "a create can load a calculation without selecting the fields the calculation needs" do resp = """ diff --git a/test/support/embeds.ex b/test/support/embeds.ex new file mode 100644 index 0000000..1cc1e76 --- /dev/null +++ b/test/support/embeds.ex @@ -0,0 +1,47 @@ +defmodule Foo do + @moduledoc false + use Ash.Resource, + data_layer: :embedded, + extensions: [ + AshGraphql.Resource + ] + + graphql do + type :foo_embed + end + + attributes do + attribute :type, :atom do + constraints(one_of: [:foo]) + writable?(false) + end + + attribute :foo, :string do + allow_nil? false + end + end +end + +defmodule Bar do + @moduledoc false + use Ash.Resource, + data_layer: :embedded, + extensions: [ + AshGraphql.Resource + ] + + graphql do + type :bar_embed + end + + attributes do + attribute :type, :atom do + constraints(one_of: [:foo]) + writable?(false) + end + + attribute :bar, :string do + allow_nil? false + end + end +end diff --git a/test/support/resources/post.ex b/test/support/resources/post.ex index 140b0fe..1237d70 100644 --- a/test/support/resources/post.ex +++ b/test/support/resources/post.ex @@ -249,6 +249,23 @@ defmodule AshGraphql.Test.Post do ] ) + attribute(:embed_union, :union, + constraints: [ + types: [ + foo: [ + type: Foo, + tag: :type, + tag_value: :foo + ], + bar: [ + type: Bar, + tag: :type, + tag_value: :bar + ] + ] + ] + ) + create_timestamp(:created_at, private?: false) end