diff --git a/lib/absinthe/federation/schema/entity_union.ex b/lib/absinthe/federation/schema/entity_union.ex index 4d8d5ac..58da8ce 100644 --- a/lib/absinthe/federation/schema/entity_union.ex +++ b/lib/absinthe/federation/schema/entity_union.ex @@ -35,7 +35,7 @@ defmodule Absinthe.Federation.Schema.EntityUnion do %{name: name, __private__: _private} = node, types ) do - if has_key_directive?(node) do + if is_object_type?(node) and has_key_directive?(node) do {node, [%Name{name: name} | types]} else {node, types} @@ -54,6 +54,9 @@ defmodule Absinthe.Federation.Schema.EntityUnion do defp is_key_directive?(%{name: "key"} = _directive), do: true defp is_key_directive?(_directive), do: false + + defp is_object_type?(%Absinthe.Blueprint.Schema.ObjectTypeDefinition{}), do: true + defp is_object_type?(_), do: false end defprotocol Absinthe.Federation.Schema.EntityUnion.Resolver do @@ -64,19 +67,34 @@ end defimpl Absinthe.Federation.Schema.EntityUnion.Resolver, for: Any do alias Absinthe.Adapter.LanguageConventions - def resolve_type(%struct_name{}, resolution) do - struct_name - |> Module.split() - |> List.last() - |> to_internal_name(resolution.adapter) + def resolve_type(%struct_name{} = data, resolution) do + typename = + struct_name + |> Module.split() + |> List.last() + + inner_resolve_type(data, typename, resolution) + end + + def resolve_type(%{__typename: typename} = data, resolution) do + inner_resolve_type(data, typename, resolution) end - def resolve_type(%{__typename: typename}, resolution) do - to_internal_name(typename, resolution.adapter) + def resolve_type(%{"__typename" => typename} = data, resolution) do + inner_resolve_type(data, typename, resolution) end - def resolve_type(%{"__typename" => typename}, resolution) do - to_internal_name(typename, resolution.adapter) + defp inner_resolve_type(data, typename, resolution) do + type = Absinthe.Schema.lookup_type(resolution.schema, typename) + + case type do + %{resolve_type: resolve_type} when not is_nil(resolve_type) -> + resolver = Absinthe.Type.function(type, :resolve_type) + resolver.(data, resolution) + + _type -> + to_internal_name(typename, resolution.adapter) + end end defp to_internal_name(name, adapter) when is_nil(adapter) do diff --git a/test/absinthe/federation/schema/entity_union_test.exs b/test/absinthe/federation/schema/entity_union_test.exs index 86cb99d..0121e66 100644 --- a/test/absinthe/federation/schema/entity_union_test.exs +++ b/test/absinthe/federation/schema/entity_union_test.exs @@ -195,4 +195,123 @@ defmodule Absinthe.Federation.Schema.EntityUnionTest do refute sdl =~ "union _Entity" end end + + describe "entity interface tests" do + defmodule MacroSchemaWithInterface do + use Absinthe.Schema + use Absinthe.Federation.Schema + + query do + field :shapes, list_of(:shape) do + resolve fn _args, _info -> + shapes = [ + %Example.EntityInterface.Circle{id: "1"}, + %Example.EntityInterface.Rectangle{id: "2"} + ] + + {:ok, shapes} + end + end + end + + interface :shape do + key_fields("id") + field :id, non_null(:id) + + field :_resolve_reference, :shape do + resolve(fn _, %{id: id} = args, _ -> + case id do + "123" -> {:ok, args} + "321" -> {:ok, args} + _ -> {:error, "ID doesn't exist #{id}"} + end + end) + end + + resolve_type fn + data, _ -> + case data do + %Example.EntityInterface.Circle{} -> :circle + %Example.EntityInterface.Rectangle{} -> :rectangle + %{id: "123"} -> :circle + %{id: "321"} -> :rectangle + _ -> nil + end + end + end + + object :circle do + key_fields("id") + field :id, non_null(:id) + + interface :shape + + field :_resolve_reference, :circle do + resolve(fn _, %{id: id} = args, _ -> + case id do + "123" -> {:ok, args} + _ -> {:error, "ID doesn't exist #{id}"} + end + end) + end + end + + object :rectangle do + key_fields("id") + field :id, non_null(:id) + + interface :shape + + field :_resolve_reference, :rectangle do + resolve(fn _, %{id: id} = args, _ -> + case id do + "321" -> {:ok, args} + _ -> {:error, "ID doesn't exist #{id}"} + end + end) + end + end + end + + test "omits interfaces with keys from the entities union" do + sdl = Absinthe.Schema.to_sdl(MacroSchemaWithInterface) + assert sdl =~ "union _Entity = Circle | Rectangle" + end + + test "correct object type returned" do + query = """ + { + _entities(representations: [{__typename: "Shape", id: "123"}, {__typename: "Shape", id: "321"}]) { + __typename + ...on Circle { + id + } + ...on Rectangle { + id + } + } + } + """ + + %{data: %{"_entities" => [circle, rectangle]}} = Absinthe.run!(query, MacroSchemaWithInterface) + + assert circle == %{"id" => "123", "__typename" => "Circle"} + assert rectangle == %{"id" => "321", "__typename" => "Rectangle"} + end + + test "correct data returned" do + query = """ + { + shapes { + id + } + } + """ + + %{data: %{"shapes" => [circle, rectangle]}} = Absinthe.run!(query, MacroSchemaWithInterface) + + assert circle == %{"id" => "1"} + assert rectangle == %{"id" => "2"} + end + end end diff --git a/test/support/entity_interface/circle.ex b/test/support/entity_interface/circle.ex new file mode 100644 index 0000000..9efb791 --- /dev/null +++ b/test/support/entity_interface/circle.ex @@ -0,0 +1,7 @@ +defmodule Example.EntityInterface.Circle do + @type t :: %__MODULE__{ + id: String.t() + } + + defstruct id: "" +end diff --git a/test/support/entity_interface/rectangle.ex b/test/support/entity_interface/rectangle.ex new file mode 100644 index 0000000..1a9f701 --- /dev/null +++ b/test/support/entity_interface/rectangle.ex @@ -0,0 +1,7 @@ +defmodule Example.EntityInterface.Rectangle do + @type t :: %__MODULE__{ + id: String.t() + } + + defstruct id: "" +end