Skip to content

Commit b177520

Browse files
committed
Add @deprecated validation for input type fields
1 parent c8f08df commit b177520

5 files changed

Lines changed: 123 additions & 2 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package graphql.schema.validation;
2+
3+
import graphql.Internal;
4+
import graphql.schema.GraphQLAppliedDirective;
5+
import graphql.schema.GraphQLInputObjectField;
6+
import graphql.schema.GraphQLInputObjectType;
7+
import graphql.schema.GraphQLSchemaElement;
8+
import graphql.schema.GraphQLTypeUtil;
9+
import graphql.schema.GraphQLTypeVisitorStub;
10+
import graphql.util.TraversalControl;
11+
import graphql.util.TraverserContext;
12+
13+
import static java.lang.String.format;
14+
15+
/*
16+
From the spec:
17+
The @deprecated directive must not appear on required (non-null without a default) arguments
18+
or input object field definitions.
19+
*/
20+
@Internal
21+
public class DeprecatedInputObjectAndArgumentsAreValid extends GraphQLTypeVisitorStub {
22+
23+
@Override
24+
public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField inputObjectField, TraverserContext<GraphQLSchemaElement> context) {
25+
// There can only be at most one @deprecated, because it is not a repeatable directive
26+
GraphQLAppliedDirective deprecatedDirective = inputObjectField.getAppliedDirective("deprecated");
27+
28+
if (deprecatedDirective != null && GraphQLTypeUtil.isNonNull(inputObjectField.getType()) && !inputObjectField.hasSetDefaultValue()) {
29+
GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) context.getParentNode();
30+
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
31+
String message = format("Required input field %s.%s cannot be deprecated.", inputObjectType.getName(), inputObjectField.getName());
32+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.RequiredInputFieldCannotBeDeprecated, message));
33+
}
34+
return TraversalControl.CONTINUE;
35+
}
36+
37+
}

src/main/java/graphql/schema/validation/SchemaValidationErrorType.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,8 @@ public enum SchemaValidationErrorType implements SchemaValidationErrorClassifica
2121
OutputTypeUsedInInputTypeContext,
2222
InputTypeUsedInOutputTypeContext,
2323
OneOfDefaultValueOnField,
24-
OneOfNonNullableField
24+
OneOfNonNullableField,
25+
RequiredInputFieldCannotBeDeprecated,
26+
RequiredFieldArgumentCannotBeDeprecated,
27+
RequiredDirectiveArgumentCannotBeDeprecated
2528
}

src/main/java/graphql/schema/validation/SchemaValidator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public SchemaValidator() {
2626
rules.add(new AppliedDirectiveArgumentsAreValid());
2727
rules.add(new InputAndOutputTypesUsedAppropriately());
2828
rules.add(new OneOfInputObjectRules());
29+
rules.add(new DeprecatedInputObjectAndArgumentsAreValid());
2930
}
3031

3132
public List<GraphQLTypeVisitor> getRules() {
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package graphql.schema.validation
2+
3+
import graphql.TestUtil
4+
import spock.lang.Specification
5+
6+
class DeprecatedInputObjectAndArgumentsAreValidTest extends Specification {
7+
8+
def "required input field cannot be deprecated"() {
9+
def sdl = '''
10+
type Query {
11+
pizza(name: String!): String
12+
}
13+
14+
type Mutation {
15+
updatePizza(pizzaInfo: PizzaInfo!): String
16+
}
17+
18+
input PizzaInfo {
19+
name: String!
20+
pineapples: Boolean! @deprecated(reason: "Don't need this input field")
21+
}
22+
'''
23+
24+
when:
25+
TestUtil.schema(sdl)
26+
27+
then:
28+
def schemaProblem = thrown(InvalidSchemaException)
29+
schemaProblem.getErrors().size() == 1
30+
schemaProblem.getErrors().first().description == "Required input field PizzaInfo.pineapples cannot be deprecated."
31+
}
32+
33+
def "nullable input field can be deprecated"() {
34+
def sdl = '''
35+
type Query {
36+
pizza(name: String!): String
37+
}
38+
39+
type Mutation {
40+
updatePizza(pizzaInfo: PizzaInfo!): String
41+
}
42+
43+
input PizzaInfo {
44+
name: String!
45+
pineapples: Boolean @deprecated(reason: "Don't need this input field")
46+
}
47+
'''
48+
49+
when:
50+
TestUtil.schema(sdl)
51+
52+
then:
53+
noExceptionThrown()
54+
}
55+
56+
def "non-nullable input field with default value can be deprecated"() {
57+
def sdl = '''
58+
type Query {
59+
pizza(name: String!): String
60+
}
61+
62+
type Mutation {
63+
updatePizza(pizzaInfo: PizzaInfo!): String
64+
}
65+
66+
input PizzaInfo {
67+
name: String!
68+
pineapples: Boolean! = false @deprecated(reason: "Don't need this input field")
69+
}
70+
'''
71+
72+
when:
73+
TestUtil.schema(sdl)
74+
75+
then:
76+
noExceptionThrown()
77+
}
78+
79+
}

src/test/groovy/graphql/schema/validation/SchemaValidatorTest.groovy

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ class SchemaValidatorTest extends Specification {
1111
def validator = new SchemaValidator()
1212
def rules = validator.rules
1313
then:
14-
rules.size() == 8
14+
rules.size() == 9
1515
rules[0] instanceof NoUnbrokenInputCycles
1616
rules[1] instanceof TypesImplementInterfaces
1717
rules[2] instanceof TypeAndFieldRule
@@ -20,5 +20,6 @@ class SchemaValidatorTest extends Specification {
2020
rules[5] instanceof AppliedDirectiveArgumentsAreValid
2121
rules[6] instanceof InputAndOutputTypesUsedAppropriately
2222
rules[7] instanceof OneOfInputObjectRules
23+
rules[8] instanceof DeprecatedInputObjectAndArgumentsAreValid
2324
}
2425
}

0 commit comments

Comments
 (0)