Skip to content

Commit

Permalink
Merge pull request #1334 from jeffutter/recurse-prototype-type-resolu…
Browse files Browse the repository at this point in the history
…tion

Fix type resolution by recursing prototypes
  • Loading branch information
benwilson512 authored Aug 20, 2024
2 parents 0ebe9cb + fad950d commit 3d0823b
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 7 deletions.
37 changes: 30 additions & 7 deletions lib/absinthe/phase/schema/compile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,27 +8,46 @@ 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 =
Map.new(schema.type_definitions, fn type_def ->
{type_def.identifier, type_def.name}
end)

type_list =
case prototype_schema do
Absinthe.Schema.Prototype ->
type_list

prototype_schema ->
Map.merge(type_list, prototype_schema.__absinthe_types__())
end

referenced_types =
for type_def <- schema.type_definitions,
type_def.__private__[:__absinthe_referenced__],
into: %{},
do: {type_def.identifier, type_def.name}

referenced_types =
case prototype_schema do
Absinthe.Schema.Prototype ->
referenced_types

prototype_schema ->
Map.merge(referenced_types, prototype_schema.__absinthe_types__(:referenced))
end

directive_list =
Map.new(schema.directive_artifacts, fn type_def ->
{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 +105,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 +127,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
25 changes: 25 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 All @@ -68,13 +81,25 @@ if Code.ensure_loaded?(:persistent_term) do
|> get()
|> Map.fetch!(:__absinthe_types__)
|> Map.fetch!(:referenced)
|> __maybe_merge_types_from_prototype(schema_mod, :referenced)
end

def __absinthe_types__(schema_mod, group) do
schema_mod
|> get()
|> Map.fetch!(:__absinthe_types__)
|> Map.fetch!(group)
|> __maybe_merge_types_from_prototype(schema_mod, group)
end

defp __maybe_merge_types_from_prototype(types, schema_mod, group) do
prototype_schema_mod = schema_mod.__absinthe_prototype_schema__()

if prototype_schema_mod == Absinthe.Schema.Prototype do
types
else
Map.merge(types, prototype_schema_mod.__absinthe_types__(group))
end
end

def __absinthe_directives__(schema_mod) do
Expand Down
154 changes: 154 additions & 0 deletions test/absinthe/introspection_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,160 @@ 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

scalar :_underscore_normal_string, name: "_UnderscoreNormalString" 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
arg :_underscore_normal_string, :_underscore_normal_string

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 {
types {
name
}
directives {
name
args {
name
description
type {
kind
name
}
defaultValue
}
}
}
}
"""
|> run(ComplexDirectiveSchema)

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

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

assert Enum.member?(types, %{"name" => "Complex"})

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

assert Enum.member?(types, %{"name" => "NormalString"})

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

assert Enum.member?(types, %{"name" => "ColorChannel"})

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

assert Enum.member?(types, %{"name" => "_UnderscoreNormalString"})
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 3d0823b

Please sign in to comment.