Skip to content

Commit

Permalink
Fix type resolution by recursing prototypes
Browse files Browse the repository at this point in the history
This fixes an issue when you have types declared in a Prototype (such as
input_object input types for a directive). Previously type resolution
would look on the Schema, but not the Prototype for the type
definitions.

You _could_ workaround this by duplicating your type definition into
both the Schema and the Prototype. This makes me somewhat curious if
there is a better way to fix this, in that the code that was trying to
resolve the type here shouldn't be resolving it on the Schema, but
rather on the Prototype 🤷. This seems to work though.

Fixes absinthe-graphql#1279
  • Loading branch information
jeffutter committed Aug 5, 2024
1 parent f4a930c commit 2e320b1
Show file tree
Hide file tree
Showing 3 changed files with 148 additions and 7 deletions.
19 changes: 12 additions & 7 deletions lib/absinthe/phase/schema/compile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@ defmodule Absinthe.Phase.Schema.Compile do

%{schema_definitions: [schema]} = blueprint

type_ast = build_types(schema.type_artifacts)
prototype_schema = Keyword.fetch!(opts, :prototype_schema)

type_ast = build_types(schema.type_artifacts, prototype_schema)

directive_ast = build_directives(schema.directive_artifacts)

type_list =
Expand All @@ -27,8 +30,6 @@ defmodule Absinthe.Phase.Schema.Compile do
{type_def.identifier, type_def.name}
end)

prototype_schema = Keyword.fetch!(opts, :prototype_schema)

metadata = build_metadata(schema)

implementors = build_implementors(schema)
Expand Down Expand Up @@ -86,7 +87,7 @@ defmodule Absinthe.Phase.Schema.Compile do
end
end

def build_types(types) do
def build_types(types, prototype_schema) do
for type <- types do
if !type.definition,
do:
Expand All @@ -108,9 +109,13 @@ defmodule Absinthe.Phase.Schema.Compile do
end
end
|> Enum.concat([
quote do
def __absinthe_type__(_type) do
nil
if prototype_schema == Absinthe.Schema.Prototype do
quote do
def __absinthe_type__(_type), do: nil
end
else
quote do
def __absinthe_type__(type), do: unquote(prototype_schema).__absinthe_type__(type)
end
end
])
Expand Down
13 changes: 13 additions & 0 deletions lib/absinthe/schema/persistent_term.ex
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,21 @@ if Code.ensure_loaded?(:persistent_term) do
|> get()
|> Map.fetch!(:__absinthe_type__)
|> Map.get(name)
|> __maybe_absinthe_type_from_prototype(name, schema_mod)
end

defp __maybe_absinthe_type_from_prototype(nil, name, schema_mod) do
prototype_schema_mod = schema_mod.__absinthe_prototype_schema__()

if prototype_schema_mod == Absinthe.Schema.Prototype do
nil
else
prototype_schema_mod.__absinthe_type__(name)
end
end

defp __maybe_absinthe_type_from_prototype(value, _, _), do: value

def __absinthe_directive__(schema_mod, name) do
schema_mod
|> get()
Expand Down
123 changes: 123 additions & 0 deletions test/absinthe/introspection_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,129 @@ defmodule Absinthe.IntrospectionTest do
end
end

describe "introspection of complex directives" do
defmodule ComplexDirectiveSchema do
use Absinthe.Schema
use Absinthe.Fixture

defmodule Utils do
def parse(value), do: value
def serialize(value), do: value
end

defmodule ComplexDirectivePrototype do
use Absinthe.Schema.Prototype

input_object :complex do
field :str, :string
end

scalar :normal_string, description: "string" do
parse &Utils.parse/1
serialize &Utils.serialize/1
end

enum :color_channel do
description "The selected color channel"
value :red, as: :r, description: "Color Red"
value :green, as: :g, description: "Color Green"
value :blue, as: :b, description: "Color Blue"
end

directive :complex_directive do
arg :complex, :complex
arg :normal_string, :normal_string
arg :color_channel, :color_channel

on [:field]
end
end

@prototype_schema ComplexDirectivePrototype

query do
field :foo,
type: :string,
args: [],
resolve: fn _, _ -> {:ok, "foo"} end
end
end

test "renders type for complex directives" do
result =
"""
query IntrospectionQuery {
__schema {
directives {
name
args {
name
description
type {
kind
name
}
defaultValue
}
}
}
}
"""
|> run(ComplexDirectiveSchema)

assert {:ok,
%{
data: %{
"__schema" => %{
"directives" => [
%{"name" => "complexDirective", "args" => complex_directive_args}
| _
]
}
}
}} = result

assert Enum.member?(
complex_directive_args,
%{
"type" => %{
"kind" => "INPUT_OBJECT",
"name" => "Complex"
},
"defaultValue" => nil,
"description" => nil,
"name" => "complex"
}
)

assert Enum.member?(
complex_directive_args,
%{
"type" => %{
"kind" => "SCALAR",
"name" => "NormalString"
},
"defaultValue" => nil,
"description" => nil,
"name" => "normalString"
}
)

assert Enum.member?(
complex_directive_args,
%{
"type" => %{
"kind" => "ENUM",
"name" => "ColorChannel"
},
"defaultValue" => nil,
"description" => nil,
"name" => "colorChannel"
}
)
end
end

describe "introspection of an enum type" do
test "can use __type and value information with deprecations" do
result =
Expand Down

0 comments on commit 2e320b1

Please sign in to comment.