Skip to content

Commit 07dd807

Browse files
committed
Add validation for directive and field arguments
1 parent b177520 commit 07dd807

2 files changed

Lines changed: 245 additions & 2 deletions

File tree

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
import graphql.Internal;
44
import graphql.schema.GraphQLAppliedDirective;
5+
import graphql.schema.GraphQLArgument;
6+
import graphql.schema.GraphQLDirective;
7+
import graphql.schema.GraphQLFieldDefinition;
58
import graphql.schema.GraphQLInputObjectField;
69
import graphql.schema.GraphQLInputObjectType;
710
import graphql.schema.GraphQLSchemaElement;
@@ -28,10 +31,33 @@ public TraversalControl visitGraphQLInputObjectField(GraphQLInputObjectField inp
2831
if (deprecatedDirective != null && GraphQLTypeUtil.isNonNull(inputObjectField.getType()) && !inputObjectField.hasSetDefaultValue()) {
2932
GraphQLInputObjectType inputObjectType = (GraphQLInputObjectType) context.getParentNode();
3033
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
31-
String message = format("Required input field %s.%s cannot be deprecated.", inputObjectType.getName(), inputObjectField.getName());
34+
String message = format("Required input field '%s.%s' cannot be deprecated.", inputObjectType.getName(), inputObjectField.getName());
3235
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.RequiredInputFieldCannotBeDeprecated, message));
3336
}
3437
return TraversalControl.CONTINUE;
3538
}
3639

40+
// An argument can appear as either a field argument or a directive argument. This visitor will visit both field arguments and directive arguments.
41+
// An applied directive's argument cannot be deprecated.
42+
@Override
43+
public TraversalControl visitGraphQLArgument(GraphQLArgument argument, TraverserContext<GraphQLSchemaElement> context) {
44+
// There can only be at most one @deprecated, because it is not a repeatable directive
45+
GraphQLAppliedDirective deprecatedDirective = argument.getAppliedDirective("deprecated");
46+
47+
if (deprecatedDirective != null && GraphQLTypeUtil.isNonNull(argument.getType()) && !argument.hasSetDefaultValue()) {
48+
if (context.getParentNode() instanceof GraphQLFieldDefinition) {
49+
GraphQLFieldDefinition fieldDefinition = (GraphQLFieldDefinition) context.getParentNode();
50+
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
51+
String message = format("Required argument '%s' on field '%s' cannot be deprecated.", argument.getName(), fieldDefinition.getName());
52+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.RequiredFieldArgumentCannotBeDeprecated, message));
53+
} else if (context.getParentNode() instanceof GraphQLDirective) {
54+
GraphQLDirective directive = (GraphQLDirective) context.getParentNode();
55+
SchemaValidationErrorCollector errorCollector = context.getVarFromParents(SchemaValidationErrorCollector.class);
56+
String message = format("Required argument '%s' on directive '%s' cannot be deprecated.", argument.getName(), directive.getName());
57+
errorCollector.addError(new SchemaValidationError(SchemaValidationErrorType.RequiredDirectiveArgumentCannotBeDeprecated, message));
58+
}
59+
}
60+
return TraversalControl.CONTINUE;
61+
}
62+
3763
}

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

Lines changed: 218 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class DeprecatedInputObjectAndArgumentsAreValidTest extends Specification {
1818
input PizzaInfo {
1919
name: String!
2020
pineapples: Boolean! @deprecated(reason: "Don't need this input field")
21+
spicy: Boolean @deprecated(reason: "Don't need this nullable input field")
2122
}
2223
'''
2324

@@ -27,7 +28,59 @@ class DeprecatedInputObjectAndArgumentsAreValidTest extends Specification {
2728
then:
2829
def schemaProblem = thrown(InvalidSchemaException)
2930
schemaProblem.getErrors().size() == 1
30-
schemaProblem.getErrors().first().description == "Required input field PizzaInfo.pineapples cannot be deprecated."
31+
schemaProblem.getErrors().first().description == "Required input field 'PizzaInfo.pineapples' cannot be deprecated."
32+
}
33+
34+
def "multiple required input fields cannot be deprecated"() {
35+
def sdl = '''
36+
type Query {
37+
pizza(name: String!): String
38+
}
39+
40+
type Mutation {
41+
updatePizza(pizzaInfo: PizzaInfo!): String
42+
}
43+
44+
input PizzaInfo {
45+
name: String!
46+
pineapples: Boolean! @deprecated(reason: "Don't need this input field")
47+
spicy: Boolean! @deprecated(reason: "Don't need this input field")
48+
}
49+
'''
50+
51+
when:
52+
TestUtil.schema(sdl)
53+
54+
then:
55+
def schemaProblem = thrown(InvalidSchemaException)
56+
schemaProblem.getErrors().size() == 2
57+
schemaProblem.getErrors()[0].description == "Required input field 'PizzaInfo.pineapples' cannot be deprecated."
58+
schemaProblem.getErrors()[1].description == "Required input field 'PizzaInfo.spicy' cannot be deprecated."
59+
}
60+
61+
def "required input field list cannot be deprecated"() {
62+
def sdl = '''
63+
type Query {
64+
pizza(name: String!): String
65+
}
66+
67+
type Mutation {
68+
updatePizza(pizzaInfos: [PizzaInfo]!): String
69+
}
70+
71+
input PizzaInfo {
72+
name: String!
73+
pineapples: Boolean! @deprecated(reason: "Don't need this input field")
74+
}
75+
'''
76+
77+
when:
78+
TestUtil.schema(sdl)
79+
80+
then:
81+
def schemaProblem = thrown(InvalidSchemaException)
82+
schemaProblem.getErrors().size() == 1
83+
schemaProblem.getErrors().first().description == "Required input field 'PizzaInfo.pineapples' cannot be deprecated."
3184
}
3285

3386
def "nullable input field can be deprecated"() {
@@ -76,4 +129,168 @@ class DeprecatedInputObjectAndArgumentsAreValidTest extends Specification {
76129
noExceptionThrown()
77130
}
78131

132+
def "required field argument cannot be deprecated"() {
133+
def sdl = '''
134+
type Query {
135+
pizza(name: String!): String
136+
}
137+
138+
type Mutation {
139+
updatePizza(name: String!, pineapples: Boolean! @deprecated(reason: "Don't need this field argument")): String
140+
}
141+
'''
142+
143+
when:
144+
TestUtil.schema(sdl)
145+
146+
then:
147+
def schemaProblem = thrown(InvalidSchemaException)
148+
schemaProblem.getErrors().size() == 1
149+
schemaProblem.getErrors().first().description == "Required argument 'pineapples' on field 'updatePizza' cannot be deprecated."
150+
}
151+
152+
def "multiple required field arguments cannot be deprecated"() {
153+
def sdl = '''
154+
type Query {
155+
pizza(name: String!): String
156+
}
157+
158+
type Mutation {
159+
updatePizza(name: String! @deprecated(reason: "yeah nah"), pineapples: Boolean! @deprecated(reason: "Don't need this field argument")): String
160+
}
161+
'''
162+
163+
when:
164+
TestUtil.schema(sdl)
165+
166+
then:
167+
def schemaProblem = thrown(InvalidSchemaException)
168+
schemaProblem.getErrors().size() == 2
169+
schemaProblem.getErrors()[0].description == "Required argument 'name' on field 'updatePizza' cannot be deprecated."
170+
schemaProblem.getErrors()[1].description == "Required argument 'pineapples' on field 'updatePizza' cannot be deprecated."
171+
}
172+
173+
def "nullable field argument can be deprecated"() {
174+
def sdl = '''
175+
type Query {
176+
pizza(name: String!): String
177+
}
178+
179+
type Mutation {
180+
updatePizza(name: String!, pineapples: Boolean @deprecated(reason: "Don't need this field argument")): String
181+
}
182+
'''
183+
184+
when:
185+
TestUtil.schema(sdl)
186+
187+
then:
188+
noExceptionThrown()
189+
}
190+
191+
def "non-nullable field argument with default value can be deprecated"() {
192+
def sdl = '''
193+
type Query {
194+
pizza(name: String!): String
195+
}
196+
197+
type Mutation {
198+
updatePizza(name: String!, pineapples: Boolean! = false @deprecated(reason: "Don't need this field argument")): String
199+
}
200+
'''
201+
202+
when:
203+
TestUtil.schema(sdl)
204+
205+
then:
206+
noExceptionThrown()
207+
}
208+
209+
def "required directive argument cannot be deprecated"() {
210+
def sdl = '''
211+
directive @pizzaDirective(name: String!, likesPineapples: Boolean! @deprecated(reason: "Don't need this directive argument")) on FIELD_DEFINITION
212+
213+
type Query {
214+
pizza(name: String!): String @pizzaDirective(name: "Stefano", likesPineapples: true)
215+
}
216+
217+
type Mutation {
218+
updatePizza(name: String!, pineapples: Boolean!): String
219+
}
220+
'''
221+
222+
when:
223+
TestUtil.schema(sdl)
224+
225+
then:
226+
def schemaProblem = thrown(InvalidSchemaException)
227+
schemaProblem.getErrors().size() == 1
228+
schemaProblem.getErrors().first().description == "Required argument 'likesPineapples' on directive 'pizzaDirective' cannot be deprecated."
229+
}
230+
231+
def "multiple required directive arguments cannot be deprecated"() {
232+
def sdl = '''
233+
directive @pizzaDirective(name: String! @deprecated, likesPineapples: Boolean! @deprecated(reason: "Don't need this directive argument")) on FIELD_DEFINITION
234+
235+
type Query {
236+
pizza(name: String!): String @pizzaDirective(name: "Stefano", likesPineapples: true)
237+
}
238+
239+
type Mutation {
240+
updatePizza(name: String!, pineapples: Boolean!): String
241+
}
242+
'''
243+
244+
when:
245+
TestUtil.schema(sdl)
246+
247+
then:
248+
def schemaProblem = thrown(InvalidSchemaException)
249+
schemaProblem.getErrors().size() == 2
250+
schemaProblem.getErrors()[0].description == "Required argument 'name' on directive 'pizzaDirective' cannot be deprecated."
251+
schemaProblem.getErrors()[1].description == "Required argument 'likesPineapples' on directive 'pizzaDirective' cannot be deprecated."
252+
}
253+
254+
def "nullable directive argument can be deprecated"() {
255+
def sdl = '''
256+
directive @pizzaDirective(name: String!, likesPineapples: Boolean @deprecated(reason: "Don't need this directive argument")) on FIELD_DEFINITION
257+
258+
type Query {
259+
pizza(name: String!): String @pizzaDirective(name: "Stefano", likesPineapples: true)
260+
}
261+
262+
type Mutation {
263+
updatePizza(name: String!, pineapples: Boolean!): String
264+
}
265+
266+
'''
267+
268+
when:
269+
TestUtil.schema(sdl)
270+
271+
then:
272+
noExceptionThrown()
273+
}
274+
275+
def "non-nullable directive argument with default value can be deprecated"() {
276+
def sdl = '''
277+
directive @pizzaDirective(name: String!, likesPineapples: Boolean! = false @deprecated(reason: "Don't need this directive argument")) on FIELD_DEFINITION
278+
279+
type Query {
280+
pizza(name: String!): String @pizzaDirective(name: "Stefano", likesPineapples: true)
281+
}
282+
283+
type Mutation {
284+
updatePizza(name: String!, pineapples: Boolean!): String
285+
}
286+
287+
'''
288+
289+
when:
290+
TestUtil.schema(sdl)
291+
292+
then:
293+
noExceptionThrown()
294+
}
295+
79296
}

0 commit comments

Comments
 (0)