From 6cc8044e2e417a2d276414f0e5ab2760aefeb751 Mon Sep 17 00:00:00 2001 From: bbaker Date: Fri, 28 Feb 2025 16:16:51 +1100 Subject: [PATCH] @defer is now auto included in the schema if missing --- .../java/graphql/schema/GraphQLSchema.java | 1 + .../graphql/schema/idl/DirectiveInfo.java | 15 +-- src/test/groovy/graphql/Issue2141.groovy | 8 ++ .../graphql/StarWarsIntrospectionTests.groovy | 2 +- ...rospectionWithDirectivesSupportTest.groovy | 4 +- .../graphql/schema/GraphQLSchemaTest.groovy | 8 +- .../schema/diffing/SchemaDiffingTest.groovy | 6 +- ...GeneratorAppliedDirectiveHelperTest.groovy | 2 + .../schema/idl/SchemaGeneratorTest.groovy | 6 +- .../schema/idl/SchemaPrinterTest.groovy | 106 +++++++++++++++++- 10 files changed, 131 insertions(+), 27 deletions(-) diff --git a/src/main/java/graphql/schema/GraphQLSchema.java b/src/main/java/graphql/schema/GraphQLSchema.java index 021932c1b..80a17777d 100644 --- a/src/main/java/graphql/schema/GraphQLSchema.java +++ b/src/main/java/graphql/schema/GraphQLSchema.java @@ -850,6 +850,7 @@ private GraphQLSchema buildImpl() { addBuiltInDirective(Directives.DeprecatedDirective, additionalDirectives); addBuiltInDirective(Directives.SpecifiedByDirective, additionalDirectives); addBuiltInDirective(Directives.OneOfDirective, additionalDirectives); + addBuiltInDirective(Directives.DeferDirective, additionalDirectives); addBuiltInDirective(Directives.ExperimentalDisableErrorPropagationDirective, additionalDirectives); // quick build - no traversing diff --git a/src/main/java/graphql/schema/idl/DirectiveInfo.java b/src/main/java/graphql/schema/idl/DirectiveInfo.java index d71e52cd4..9b8317f97 100644 --- a/src/main/java/graphql/schema/idl/DirectiveInfo.java +++ b/src/main/java/graphql/schema/idl/DirectiveInfo.java @@ -15,16 +15,6 @@ @PublicApi public class DirectiveInfo { - /** - * A set of directives which provided by graphql specification - */ - public static final Set GRAPHQL_SPECIFICATION_DIRECTIVES = ImmutableSet.of( - Directives.IncludeDirective, - Directives.SkipDirective, - Directives.DeprecatedDirective, - Directives.SpecifiedByDirective, - Directives.OneOfDirective - ); /** * A map from directive name to directive which provided by specification @@ -37,8 +27,13 @@ public class DirectiveInfo { Directives.OneOfDirective.getName(), Directives.OneOfDirective, // technically this is NOT yet in spec - but it is added by default by graphql-java so we include it // we should also do @defer at some future time soon + Directives.DeferDirective.getName(), Directives.DeferDirective, Directives.ExperimentalDisableErrorPropagationDirective.getName(), Directives.ExperimentalDisableErrorPropagationDirective ); + /** + * A set of directives which provided by graphql specification + */ + public static final Set GRAPHQL_SPECIFICATION_DIRECTIVES =ImmutableSet.copyOf(GRAPHQL_SPECIFICATION_DIRECTIVE_MAP.values()); /** * Returns true if a directive with provided directiveName has been defined in graphql specification diff --git a/src/test/groovy/graphql/Issue2141.groovy b/src/test/groovy/graphql/Issue2141.groovy index efa74969e..c3b00df8d 100644 --- a/src/test/groovy/graphql/Issue2141.groovy +++ b/src/test/groovy/graphql/Issue2141.groovy @@ -24,6 +24,14 @@ class Issue2141 extends Specification { then: schemaStr == '''directive @auth(roles: [String!]) on FIELD_DEFINITION +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" diff --git a/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy b/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy index 89fc78113..75411f6af 100644 --- a/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy +++ b/src/test/groovy/graphql/StarWarsIntrospectionTests.groovy @@ -430,6 +430,6 @@ class StarWarsIntrospectionTests extends Specification { schemaParts.get('mutationType').size() == 1 schemaParts.get('subscriptionType') == null schemaParts.get('types').size() == 17 - schemaParts.get('directives').size() == 6 + schemaParts.get('directives').size() == 7 } } diff --git a/src/test/groovy/graphql/introspection/IntrospectionWithDirectivesSupportTest.groovy b/src/test/groovy/graphql/introspection/IntrospectionWithDirectivesSupportTest.groovy index 3f3871798..591a44146 100644 --- a/src/test/groovy/graphql/introspection/IntrospectionWithDirectivesSupportTest.groovy +++ b/src/test/groovy/graphql/introspection/IntrospectionWithDirectivesSupportTest.groovy @@ -92,7 +92,7 @@ class IntrospectionWithDirectivesSupportTest extends Specification { schemaType["directives"] == [ [name: "include"], [name: "skip"], [name: "example"], [name: "secret"], [name: "noDefault"], [name: "deprecated"], [name: "specifiedBy"], [name: "oneOf"], - [name: "experimental_disableErrorPropagation"] + [name: "defer"], [name: "experimental_disableErrorPropagation"] ] schemaType["appliedDirectives"] == [[name: "example", args: [[name: "argName", value: '"onSchema"']]]] @@ -175,7 +175,7 @@ class IntrospectionWithDirectivesSupportTest extends Specification { def definedDirectives = er.data["__schema"]["directives"] // secret is filter out definedDirectives == [[name: "include"], [name: "skip"], [name: "example"], [name: "deprecated"], [name: "specifiedBy"], [name: "oneOf"], - [name: "experimental_disableErrorPropagation"] + [name: "defer"], [name: "experimental_disableErrorPropagation"] ] } diff --git a/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy b/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy index 0d75a9af4..73a04346f 100644 --- a/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy +++ b/src/test/groovy/graphql/schema/GraphQLSchemaTest.groovy @@ -155,22 +155,22 @@ class GraphQLSchemaTest extends Specification { when: "no additional directives have been specified" def schema = schemaBuilder.build() then: - schema.directives.size() == 6 + schema.directives.size() == 7 when: "clear directives is called" schema = schemaBuilder.clearDirectives().build() then: - schema.directives.size() == 4 // @deprecated and @specifiedBy and @oneOf et al is ALWAYS added if missing + schema.directives.size() == 5 // @deprecated and @specifiedBy and @oneOf et al is ALWAYS added if missing when: "clear directives is called with more directives" schema = schemaBuilder.clearDirectives().additionalDirective(Directives.SkipDirective).build() then: - schema.directives.size() == 5 + schema.directives.size() == 6 when: "the schema is transformed, things are copied" schema = schema.transform({ builder -> builder.additionalDirective(Directives.IncludeDirective) }) then: - schema.directives.size() == 6 + schema.directives.size() == 7 } def "clear additional types works as expected"() { diff --git a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy index 629d35166..46481fa7a 100644 --- a/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy +++ b/src/test/groovy/graphql/schema/diffing/SchemaDiffingTest.groovy @@ -35,13 +35,13 @@ class SchemaDiffingTest extends Specification { schemaGraph.getVerticesByType(SchemaGraph.UNION).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.SCALAR).size() == 2 schemaGraph.getVerticesByType(SchemaGraph.FIELD).size() == 40 - schemaGraph.getVerticesByType(SchemaGraph.ARGUMENT).size() == 9 + schemaGraph.getVerticesByType(SchemaGraph.ARGUMENT).size() == 11 schemaGraph.getVerticesByType(SchemaGraph.INPUT_FIELD).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.INPUT_OBJECT).size() == 0 - schemaGraph.getVerticesByType(SchemaGraph.DIRECTIVE).size() == 6 + schemaGraph.getVerticesByType(SchemaGraph.DIRECTIVE).size() == 7 schemaGraph.getVerticesByType(SchemaGraph.APPLIED_ARGUMENT).size() == 0 schemaGraph.getVerticesByType(SchemaGraph.APPLIED_DIRECTIVE).size() == 0 - schemaGraph.size() == 94 + schemaGraph.size() == 97 } diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy index 944de7589..f42cc4905 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorAppliedDirectiveHelperTest.groovy @@ -55,6 +55,7 @@ class SchemaGeneratorAppliedDirectiveHelperTest extends Specification { schema.getDirectives().collect {it.name}.sort() == [ "bar", "complex", + "defer", "deprecated", "experimental_disableErrorPropagation", "foo", @@ -106,6 +107,7 @@ class SchemaGeneratorAppliedDirectiveHelperTest extends Specification { schema.getDirectives().collect {it.name}.sort() == [ "bar", "complex", + "defer", "deprecated", "experimental_disableErrorPropagation", "foo", diff --git a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy index 1eb98cc82..9eeed6c01 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaGeneratorTest.groovy @@ -2025,10 +2025,11 @@ class SchemaGeneratorTest extends Specification { directives = schema.getDirectives() then: - directives.size() == 9 // built in ones : include / skip and deprecated + directives.size() == 10 // built in ones : include / skip and deprecated def directiveNames = directives.collect { it.name } directiveNames.contains("include") directiveNames.contains("skip") + directiveNames.contains("defer") directiveNames.contains("deprecated") directiveNames.contains("specifiedBy") directiveNames.contains("oneOf") @@ -2040,9 +2041,10 @@ class SchemaGeneratorTest extends Specification { directivesMap = schema.getDirectivesByName() then: - directivesMap.size() == 9 // built in ones + directivesMap.size() == 10 // built in ones directivesMap.containsKey("include") directivesMap.containsKey("skip") + directivesMap.containsKey("defer") directivesMap.containsKey("deprecated") directivesMap.containsKey("oneOf") directivesMap.containsKey("sd1") diff --git a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy index bb311d696..287024432 100644 --- a/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy +++ b/src/test/groovy/graphql/schema/idl/SchemaPrinterTest.groovy @@ -965,6 +965,14 @@ type Query { // args and directives are sorted like the rest of the schema printer result == '''directive @argDirective on ARGUMENT_DEFINITION +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -1144,7 +1152,15 @@ input SomeInput { then: // args and directives are sorted like the rest of the schema printer - result == '''"Marks the field, argument, input field or enum value as deprecated" + result == '''"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported" @@ -1243,7 +1259,15 @@ type Query { def resultWithDirectives = new SchemaPrinter(defaultOptions().includeDirectives(true)).print(schema) then: - resultWithDirectives == '''"Marks the field, argument, input field or enum value as deprecated" + resultWithDirectives == '''"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported" @@ -1314,7 +1338,15 @@ type Query { def resultWithDirectiveDefinitions = new SchemaPrinter(defaultOptions().includeDirectiveDefinitions(true)).print(schema) then: - resultWithDirectiveDefinitions == '''"Marks the field, argument, input field or enum value as deprecated" + resultWithDirectiveDefinitions == '''"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported" @@ -1414,6 +1446,14 @@ extend schema { subscription: MySubscription } +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -1503,6 +1543,14 @@ schema @schemaDirective{ mutation: MyMutation } +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -1646,7 +1694,15 @@ type MyQuery { def result = new SchemaPrinter(printOptions).print(schema) then: - result == '''"Marks the field, argument, input field or enum value as deprecated" + result == '''"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported" @@ -2185,6 +2241,14 @@ type PrintMeType { query: MyQuery } +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -2429,6 +2493,14 @@ directive @deprecated( reason: String! = "No longer supported" ) on FIELD_DEFINITION | ARGUMENT_DEFINITION | ENUM_VALUE | INPUT_FIELD_DEFINITION +"This directive allows results to be deferred during execution" +directive @defer( + "A unique label that represents the fragment being deferred" + label: String, + "Deferred behaviour is controlled by this argument" + if: Boolean! = true + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + union ZUnion = XQuery | Query scalar ZScalar @@ -2539,6 +2611,14 @@ schema { mutation: Mutation } +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -2779,6 +2859,14 @@ schema { mutation: Mutation } +"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + "Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" @@ -2970,7 +3058,15 @@ input Input { expect: "has no skip definition" - result == """"Marks the field, argument, input field or enum value as deprecated" + result == """"This directive allows results to be deferred during execution" +directive @defer( + "Deferred behaviour is controlled by this argument" + if: Boolean! = true, + "A unique label that represents the fragment being deferred" + label: String + ) on FRAGMENT_SPREAD | INLINE_FRAGMENT + +"Marks the field, argument, input field or enum value as deprecated" directive @deprecated( "The reason for the deprecation" reason: String! = "No longer supported"