Skip to content

Commit

Permalink
Allow interface fields to reference other interfaces (absinthe-graphq…
Browse files Browse the repository at this point in the history
  • Loading branch information
jerelmiller committed Jul 30, 2021
1 parent 62483e4 commit c4ecc82
Show file tree
Hide file tree
Showing 3 changed files with 129 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,18 @@ defmodule Absinthe.Phase.Schema.Validation.ObjectMustImplementInterfaces do
:ok
end

defp check_covariant(
%Blueprint.TypeReference.Name{name: iface_name},
%Blueprint.TypeReference.Name{name: type_name},
field_ident,
types
) do
{_, itype} = Enum.find(types, fn {_, %{name: name}} -> name == iface_name end)
{_, type} = Enum.find(types, fn {_, %{name: name}} -> name == type_name end)

check_covariant(itype, type, field_ident, types)
end

defp check_covariant(nil, _, field_ident, _), do: {:error, field_ident}
defp check_covariant(_, nil, field_ident, _), do: {:error, field_ident}

Expand Down
28 changes: 28 additions & 0 deletions test/absinthe/schema/notation/experimental/import_sdl_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ defmodule Absinthe.Schema.Notation.Experimental.ImportSdlTest do
scalarEcho(input: CoolScalar): CoolScalar
namedThings: [Named]
titledThings: [Titled]
playerField: PlayerInterface
}
scalar CoolScalar
Expand Down Expand Up @@ -97,6 +98,22 @@ defmodule Absinthe.Schema.Notation.Experimental.ImportSdlTest do
duration: Int!
}
interface PlayerInterface {
metadata: PlayerMetadataInterface
}
interface PlayerMetadataInterface {
displayName: String
}
type HumanPlayer implements PlayerInterface {
metadata: HumanMetadata
}
type HumanMetadata implements PlayerMetadataInterface {
displayName: String
}
scalar B
union SearchResult = Post | User
Expand Down Expand Up @@ -173,6 +190,14 @@ defmodule Absinthe.Schema.Notation.Experimental.ImportSdlTest do
[{:resolve_type, &__MODULE__.titled_resolve_type/2}]
end

def hydrate(%{identifier: :player_interface}, _) do
[{:resolve_type, &__MODULE__.player_interface/2}]
end

def hydrate(%{identifier: :player_metadata_interface}, _) do
[{:resolve_type, &__MODULE__.player_metadata_interface/2}]
end

def hydrate(%{identifier: :content}, _) do
[{:resolve_type, &__MODULE__.content_resolve_type/2}]
end
Expand Down Expand Up @@ -231,6 +256,9 @@ defmodule Absinthe.Schema.Notation.Experimental.ImportSdlTest do

def parse_cool_scalar(value), do: {:ok, value}
def serialize_cool_scalar(%{value: value}), do: value

def player_interface(_, _), do: :human_player
def player_metadata_interface(_, _), do: :human_metadata
end

describe "custom prototype schema" do
Expand Down
124 changes: 89 additions & 35 deletions test/absinthe/schema/rule/object_must_implement_interfaces_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,73 @@ defmodule Absinthe.Schema.Rule.ObjectMustImplementInterfacesTest do
use Absinthe.Schema.Notation

object :user do
interface :named
interface :favorite_foods
field :name, :string
field :id, :id
field :parent, :named
field :another_parent, :user
field :color, non_null(list_of(non_null(:string)))
interface(:named)
interface(:favorite_foods)
field(:name, :string)
field(:id, :id)
field(:parent, :named)
field(:another_parent, :user)
field(:color, non_null(list_of(non_null(:string))))
end
end

defmodule Schema do
use Absinthe.Schema
import_types Types
import_types(Types)

interface :parented do
field :parent, :named
field :another_parent, :named
field(:parent, :named)
field(:another_parent, :named)
end

interface :named do
interface :parented
field :name, :string
field :parent, :named
field :another_parent, :named
interface(:parented)
field(:name, :string)
field(:parent, :named)
field(:another_parent, :named)

resolve_type fn
resolve_type(fn
%{type: :dog}, _ -> :dog
%{type: :user}, _ -> :user
%{type: :cat}, _ -> :cat
_, _ -> nil
end
end)
end

interface :favorite_foods do
field :color, list_of(:string)
field(:color, list_of(:string))

resolve_type fn
resolve_type(fn
%{type: :dog}, _ -> :dog
%{type: :user}, _ -> :user
%{type: :cat}, _ -> :cat
_, _ -> nil
end
end)
end

object :dog do
field :name, :string
interface :named
interface :favorite_foods
field :parent, :named
field :another_parent, :user
field :color, list_of(non_null(:string))
field(:name, :string)
interface(:named)
interface(:favorite_foods)
field(:parent, :named)
field(:another_parent, :user)
field(:color, list_of(non_null(:string)))
end

# An object field type is a valid sub-type if it is a Non-Null variant of a
# valid sub-type of the interface field type.
object :cat do
interface :named
interface :favorite_foods
field :name, non_null(:string)
field :parent, :named
field :another_parent, :user
field :color, non_null(list_of(:string))
interface(:named)
interface(:favorite_foods)
field(:name, non_null(:string))
field(:parent, :named)
field(:another_parent, :user)
field(:color, non_null(list_of(:string)))
end

query do
field :user, :user do
resolve fn _, _ ->
resolve(fn _, _ ->
{:ok,
%{
type: :user,
Expand All @@ -80,7 +80,7 @@ defmodule Absinthe.Schema.Rule.ObjectMustImplementInterfacesTest do
parent: %{type: :user, id: "def-456", name: "Parent User"},
another_parent: %{type: :user, id: "ghi-789", name: "Another Parent"}
}}
end
end)
end
end
end
Expand All @@ -97,7 +97,7 @@ defmodule Absinthe.Schema.Rule.ObjectMustImplementInterfacesTest do
defmodule InterfaceImplementsInterfaces do
use Absinthe.Schema

import_sdl """
import_sdl("""
interface Node {
id: ID!
}
Expand All @@ -113,7 +113,7 @@ defmodule Absinthe.Schema.Rule.ObjectMustImplementInterfacesTest do
thumbnail: String
}
"""
""")

query do
end
Expand All @@ -128,6 +128,60 @@ defmodule Absinthe.Schema.Rule.ObjectMustImplementInterfacesTest do
InterfaceImplementsInterfaces.__absinthe_interface_implementors__()
end

defmodule InterfaceFieldsReferenceInterfaces do
use Absinthe.Schema

import_sdl("""
interface Pet {
food: PetFood!
}
interface PetFood {
brand: String!
}
type Dog implements Pet {
food: DogFood!
}
type DogFood implements PetFood {
brand: String!
}
type Cat implements Pet {
food: CatFood!
}
type CatFood implements PetFood {
brand: String!
}
""")

query do
end

def hydrate(%{identifier: :pet}, _) do
[{:resolve_type, &__MODULE__.pet/2}]
end

def hydrate(%{identifier: :pet_food}, _) do
[{:resolve_type, &__MODULE__.pet_food/2}]
end

def hydrate(_, _), do: []

def pet(_, _), do: nil
def pet_food(_, _), do: nil
end

test "interface fields can reference other interfaces" do
assert %{
pet: [:cat, :dog],
pet_food: [:cat_food, :dog_food]
} ==
InterfaceFieldsReferenceInterfaces.__absinthe_interface_implementors__()
end

test "is enforced" do
assert_schema_error("invalid_interface_types", [
%{
Expand Down

0 comments on commit c4ecc82

Please sign in to comment.