Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Resolve Entity Interface types #106

Merged
merged 6 commits into from
Jan 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 28 additions & 10 deletions lib/absinthe/federation/schema/entity_union.ex
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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
Expand All @@ -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
Expand Down
119 changes: 119 additions & 0 deletions test/absinthe/federation/schema/entity_union_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -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
7 changes: 7 additions & 0 deletions test/support/entity_interface/circle.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Example.EntityInterface.Circle do
@type t :: %__MODULE__{
id: String.t()
}

defstruct id: ""
end
7 changes: 7 additions & 0 deletions test/support/entity_interface/rectangle.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule Example.EntityInterface.Rectangle do
@type t :: %__MODULE__{
id: String.t()
}

defstruct id: ""
end
Loading