From 2e320b10823266b5cd7f3bc9ef646c3dcf940eb5 Mon Sep 17 00:00:00 2001 From: Jeffery Utter Date: Thu, 1 Aug 2024 23:33:37 -0500 Subject: [PATCH] Fix type resolution by recursing prototypes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 #1279 --- lib/absinthe/phase/schema/compile.ex | 19 ++-- lib/absinthe/schema/persistent_term.ex | 13 +++ test/absinthe/introspection_test.exs | 123 +++++++++++++++++++++++++ 3 files changed, 148 insertions(+), 7 deletions(-) diff --git a/lib/absinthe/phase/schema/compile.ex b/lib/absinthe/phase/schema/compile.ex index 5046119371..7e36f6f073 100644 --- a/lib/absinthe/phase/schema/compile.ex +++ b/lib/absinthe/phase/schema/compile.ex @@ -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 = @@ -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) @@ -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: @@ -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 ]) diff --git a/lib/absinthe/schema/persistent_term.ex b/lib/absinthe/schema/persistent_term.ex index 7effeaef11..7655c452b5 100644 --- a/lib/absinthe/schema/persistent_term.ex +++ b/lib/absinthe/schema/persistent_term.ex @@ -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() diff --git a/test/absinthe/introspection_test.exs b/test/absinthe/introspection_test.exs index c2e5dd1eba..e8db1e1aef 100644 --- a/test/absinthe/introspection_test.exs +++ b/test/absinthe/introspection_test.exs @@ -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 =