diff --git a/guides/schema/definition.md b/guides/schema/definition.md index d4250e255d..3cf0faad8c 100644 --- a/guides/schema/definition.md +++ b/guides/schema/definition.md @@ -193,3 +193,14 @@ class MySchema < GraphQL::Schema use(GraphQL::Tracing::NewRelicTracing) end ``` + +## Extra Types + +Documentation-only types can be attached to the schema using {{ "Schema.extra_types" | api_doc }}. Types passed to this method will _always_ be available in introspection queries and SDL print-outs. + +```ruby +class MySchema < GraphQL::Schema + # These aren't for queries, but will appear in documentation: + extra_types SystemErrorType, RateLimitExceptionType +end +``` diff --git a/lib/graphql/introspection/entry_points.rb b/lib/graphql/introspection/entry_points.rb index 63bb1b6bd5..252997753a 100644 --- a/lib/graphql/introspection/entry_points.rb +++ b/lib/graphql/introspection/entry_points.rb @@ -9,13 +9,19 @@ class EntryPoints < Introspection::BaseObject def __schema # Apply wrapping manually since this field isn't wrapped by instrumentation - schema = @context.query.schema + schema = context.schema schema_type = schema.introspection_system.types["__Schema"] - schema_type.wrap(schema, @context) + schema_type.wrap(schema, context) end def __type(name:) - context.warden.reachable_type?(name) ? context.warden.get_type(name) : nil + if context.warden.reachable_type?(name) + context.warden.get_type(name) + elsif (type = context.schema.extra_types.find { |t| t.graphql_name == name }) + type + else + nil + end end end end diff --git a/lib/graphql/introspection/schema_type.rb b/lib/graphql/introspection/schema_type.rb index bfefecbd9c..d317d0b3ee 100644 --- a/lib/graphql/introspection/schema_type.rb +++ b/lib/graphql/introspection/schema_type.rb @@ -20,7 +20,9 @@ def schema_description end def types - @context.warden.reachable_types.sort_by(&:graphql_name) + types = context.warden.reachable_types + context.schema.extra_types + types.sort_by!(&:graphql_name) + types end def query_type diff --git a/lib/graphql/language/document_from_schema_definition.rb b/lib/graphql/language/document_from_schema_definition.rb index ed82a7df9f..a5bf00a9f4 100644 --- a/lib/graphql/language/document_from_schema_definition.rb +++ b/lib/graphql/language/document_from_schema_definition.rb @@ -266,8 +266,7 @@ def build_definition_nodes end definitions = build_directive_nodes(dirs_to_build) - type_nodes = build_type_definition_nodes(warden.reachable_types) - + type_nodes = build_type_definition_nodes(warden.reachable_types + schema.extra_types) if @include_one_of # This may have been set to true when iterating over all types definitions.concat(build_directive_nodes([GraphQL::Schema::Directive::OneOf])) diff --git a/lib/graphql/schema.rb b/lib/graphql/schema.rb index 239542b7ea..deaca9cb84 100644 --- a/lib/graphql/schema.rb +++ b/lib/graphql/schema.rb @@ -814,6 +814,26 @@ def disable_type_introspection_entry_point? end end + # @param new_extra_types [Module] Type definitions to include in printing and introspection, even though they aren't referenced in the schema + # @return [Array] Type definitions added to this schema + def extra_types(*new_extra_types) + if new_extra_types.any? + new_extra_types = new_extra_types.flatten + @own_extra_types ||= [] + @own_extra_types.concat(new_extra_types) + end + inherited_et = find_inherited_value(:extra_types, nil) + if inherited_et + if @own_extra_types + inherited_et + @own_extra_types + else + inherited_et + end + else + @own_extra_types || EMPTY_ARRAY + end + end + def orphan_types(*new_orphan_types) if new_orphan_types.any? new_orphan_types = new_orphan_types.flatten diff --git a/spec/graphql/schema/introspection_system_spec.rb b/spec/graphql/schema/introspection_system_spec.rb index 5fb47bd81e..4e4df20468 100644 --- a/spec/graphql/schema/introspection_system_spec.rb +++ b/spec/graphql/schema/introspection_system_spec.rb @@ -120,6 +120,14 @@ res = Jazz::Schema.execute('{ __type(name: "Ensemble") { fields { name } } }', context: context) assert res["data"]["__type"]["fields"].any? { |i| i["name"] == "overriddenName" } end + + it "includes extra types" do + res = Jazz::Schema.execute('{ __type(name: "BlogPost") { name } }') + assert_equal "BLOGPOST", res["data"]["__type"]["name"] + res2 = Jazz::Schema.execute("{ __schema { types { name } } }") + names = res2["data"]["__schema"]["types"].map { |t| t["name"] } + assert_includes names, "BLOGPOST" + end end describe "copying the built-ins" do diff --git a/spec/graphql/schema/printer_spec.rb b/spec/graphql/schema/printer_spec.rb index 554cd438ca..869afe7f6c 100644 --- a/spec/graphql/schema/printer_spec.rb +++ b/spec/graphql/schema/printer_spec.rb @@ -67,6 +67,12 @@ class Media < GraphQL::Schema::Union possible_types Image, Audio end + class MediaRating < GraphQL::Schema::Enum + value :AWESOME + value :MEH + value :BOO_HISS + end + class NoFields < GraphQL::Schema::Object end @@ -109,6 +115,7 @@ class Subscription < GraphQL::Schema::Object mutation(Mutation) subscription(Subscription) orphan_types [Media] + extra_types [MediaRating] end let(:schema) { PrinterTestSchema } @@ -540,6 +547,12 @@ class Subscription < GraphQL::Schema::Object """ union Media = Audio | Image +enum MediaRating { + AWESOME + BOO_HISS + MEH +} + type Mutation { """ Create a blog post @@ -642,6 +655,12 @@ def foobar it "applies an `only` filter" do expected = <