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..861520b00 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 correctly overrides existing deprecated directive reasons"() { + 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") +} +""" + } }