From a5614b3e8bc324a34e4883a70a4678c355b4c715 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 5 Jan 2025 17:14:18 +1100 Subject: [PATCH 1/2] Update deprecated reason if updated by schema transformer --- .../graphql/schema/idl/SchemaPrinter.java | 51 +++++++++++---- .../schema/SchemaTransformerTest.groovy | 63 +++++++++++++++++++ 2 files changed, 101 insertions(+), 13 deletions(-) diff --git a/src/main/java/graphql/schema/idl/SchemaPrinter.java b/src/main/java/graphql/schema/idl/SchemaPrinter.java index c51e4a78e..cafa142e0 100644 --- a/src/main/java/graphql/schema/idl/SchemaPrinter.java +++ b/src/main/java/graphql/schema/idl/SchemaPrinter.java @@ -945,7 +945,7 @@ public String directivesString(Class parentType, String directivesString(Class parentType, boolean isDeprecated, GraphQLDirectiveContainer directiveContainer) { List directives; if (isDeprecated) { - directives = addDeprecatedDirectiveIfNeeded(directiveContainer); + directives = addOrUpdateDeprecatedDirectiveIfNeeded(directiveContainer); } else { directives = DirectivesUtil.toAppliedDirectives(directiveContainer); } @@ -1041,25 +1041,50 @@ private boolean hasDeprecatedDirective(List directives) .count() == 1; } - private List addDeprecatedDirectiveIfNeeded(GraphQLDirectiveContainer directiveContainer) { + private List addOrUpdateDeprecatedDirectiveIfNeeded(GraphQLDirectiveContainer directiveContainer) { List directives = DirectivesUtil.toAppliedDirectives(directiveContainer); + String reason = getDeprecationReason(directiveContainer); + if (!hasDeprecatedDirective(directives) && isDeprecatedDirectiveAllowed()) { directives = new ArrayList<>(directives); - String reason = getDeprecationReason(directiveContainer); - GraphQLAppliedDirectiveArgument arg = GraphQLAppliedDirectiveArgument.newArgument() - .name("reason") - .valueProgrammatic(reason) - .type(GraphQLString) - .build(); - GraphQLAppliedDirective directive = GraphQLAppliedDirective.newDirective() - .name("deprecated") - .argument(arg) - .build(); - directives.add(directive); + directives.add(createDeprecatedDirective(reason)); + } else if (hasDeprecatedDirective(directives) && isDeprecatedDirectiveAllowed()) { + // Update deprecated reason in case modified by schema transform + directives = updateDeprecatedDirective(directives, reason); } return directives; } + private GraphQLAppliedDirective createDeprecatedDirective(String reason) { + GraphQLAppliedDirectiveArgument arg = GraphQLAppliedDirectiveArgument.newArgument() + .name("reason") + .valueProgrammatic(reason) + .type(GraphQLString) + .build(); + return GraphQLAppliedDirective.newDirective() + .name("deprecated") + .argument(arg) + .build(); + } + + private List updateDeprecatedDirective(List directives, String reason) { + GraphQLAppliedDirectiveArgument newArg = GraphQLAppliedDirectiveArgument.newArgument() + .name("reason") + .valueProgrammatic(reason) + .type(GraphQLString) + .build(); + + return directives.stream().map(d -> { + if (isDeprecatedDirective(d)) { + // Don't include reason is deliberately replaced with NOT_SET, for example in Anonymizer + if (d.getArgument("reason").getArgumentValue() != InputValueWithState.NOT_SET) { + return d.transform(builder -> builder.argument(newArg)); + } + } + return d; + }).collect(toList()); + } + private String getDeprecationReason(GraphQLDirectiveContainer directiveContainer) { if (directiveContainer instanceof GraphQLFieldDefinition) { GraphQLFieldDefinition type = (GraphQLFieldDefinition) directiveContainer; diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 339378f7a..8a7bca9a9 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -959,4 +959,67 @@ type Query { visitedSchema == schema visitedCodeRegistry instanceof GraphQLCodeRegistry.Builder } + + def "deprecation transformation overrides existing deprecated directive reason for object type"() { + def schema = TestUtil.schema(""" + schema { + query: QueryType + } + + type QueryType { + a: String + b: String @deprecated(reason: "Replace this doc") + } + + interface InterfaceType { + a: String + b: String @deprecated(reason: "Replace this doc") + } + + input InputType { + a: String + b: String @deprecated(reason: "Replace this doc") + } + """) + + when: + def typeVisitor = new GraphQLTypeVisitorStub() { + @Override + TraversalControl visitGraphQLFieldDefinition(GraphQLFieldDefinition node, TraverserContext context) { + def n = node.transform(b -> b.deprecate("NEW REASON")); + return changeNode(context, n); + } + + @Override + TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField node, TraverserContext context) { + def n = node.transform(b -> b.deprecate("NEW REASON")); + return changeNode(context, n); + } + } + def newSchema = SchemaTransformer.transformSchema(schema, typeVisitor) + + then: + def newQueryType = newSchema.getObjectType("QueryType") + def newQueryTypePrinted = new SchemaPrinter().print(newQueryType) + + newQueryTypePrinted == """type QueryType { + a: String @deprecated(reason : "NEW REASON") + b: String @deprecated(reason : "NEW REASON") +} +""" + def newInterfaceType = newSchema.getType("InterfaceType") + def newInterfaceTypePrinted = new SchemaPrinter().print(newInterfaceType) + newInterfaceTypePrinted == """interface InterfaceType { + a: String @deprecated(reason : "NEW REASON") + b: String @deprecated(reason : "NEW REASON") +} +""" + def newInputType = newSchema.getType("InputType") + def newInputTypePrinted = new SchemaPrinter().print(newInputType) + newInputTypePrinted == """input InputType { + a: String @deprecated(reason : "NEW REASON") + b: String @deprecated(reason : "NEW REASON") +} +""" + } } From a54f7b91b791f6b6fb94e875265e7252b0181455 Mon Sep 17 00:00:00 2001 From: dondonz <13839920+dondonz@users.noreply.github.com> Date: Sun, 5 Jan 2025 17:23:16 +1100 Subject: [PATCH 2/2] Tidy up --- src/test/groovy/graphql/schema/SchemaTransformerTest.groovy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy index 8a7bca9a9..861520b00 100644 --- a/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy +++ b/src/test/groovy/graphql/schema/SchemaTransformerTest.groovy @@ -960,7 +960,7 @@ type Query { visitedCodeRegistry instanceof GraphQLCodeRegistry.Builder } - def "deprecation transformation overrides existing deprecated directive reason for object type"() { + def "deprecation transformation correctly overrides existing deprecated directive reasons"() { def schema = TestUtil.schema(""" schema { query: QueryType