Skip to content

Commit 03ea8b0

Browse files
committed
Merge pull request #22 from jhgg/master
#6 ProvidedNonNullArguments
2 parents 1f0c725 + d4f4c8d commit 03ea8b0

File tree

5 files changed

+290
-6
lines changed

5 files changed

+290
-6
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,6 @@ docs/_build/
5959
# PyBuilder
6060
target/
6161

62+
# IntelliJ
63+
.idea
64+

graphql/core/validation/rules.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from ..utils import type_from_ast
22
from ..error import GraphQLError
3-
from ..type.definition import is_composite_type, is_input_type, is_leaf_type
3+
from ..type.definition import is_composite_type, is_input_type, is_leaf_type, GraphQLNonNull
44
from ..language import ast
55
from ..language.visitor import Visitor
66
from ..language.printer import print_ast
@@ -300,7 +300,52 @@ class ArgumentsOfCorrectType(ValidationRule):
300300

301301

302302
class ProvidedNonNullArguments(ValidationRule):
303-
pass
303+
def leave_Field(self, node, key, parent, path, ancestors):
304+
field_def = self.context.get_field_def()
305+
if not field_def:
306+
return False
307+
308+
errors = []
309+
arg_asts = node.arguments or []
310+
arg_ast_map = {arg.name.value: arg for arg in arg_asts}
311+
312+
for arg_def in field_def.args:
313+
arg_ast = arg_ast_map.get(arg_def.name, None)
314+
if not arg_ast and isinstance(arg_def.type, GraphQLNonNull):
315+
errors.append(GraphQLError(
316+
self.missing_field_arg_message(node.name.value, arg_def.name, arg_def.type),
317+
[node]
318+
))
319+
if errors:
320+
return errors
321+
322+
def leave_Directive(self, node, key, parent, path, ancestors):
323+
directive_def = self.context.get_directive()
324+
if not directive_def:
325+
return False
326+
327+
errors = []
328+
arg_asts = node.arguments or []
329+
arg_ast_map = {arg.name.value: arg for arg in arg_asts}
330+
331+
for arg_def in directive_def.args:
332+
arg_ast = arg_ast_map.get(arg_def.name, None)
333+
if not arg_ast and isinstance(arg_def.type, GraphQLNonNull):
334+
errors.append(GraphQLError(
335+
self.missing_directive_arg_message(node.name.value, arg_def.name, arg_def.type),
336+
[node]
337+
))
338+
339+
if errors:
340+
return errors
341+
342+
@staticmethod
343+
def missing_field_arg_message(name, arg_name, type):
344+
return 'Field "{}" argument "{}" of type "{}" is required but not provided.'.format(name, arg_name, type)
345+
346+
@staticmethod
347+
def missing_directive_arg_message(name, arg_name, type):
348+
return 'Directive "{}" argument "{}" of type "{}" is required but not provided.'.format(name, arg_name, type)
304349

305350

306351
class DefaultValuesOfCorrectType(ValidationRule):

tests/core_type/test_introspection.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import json
22
from graphql.core import graphql
3+
from graphql.core.error import format_error
4+
from graphql.core.language.location import SourceLocation
35
from graphql.core.language.parser import parse
46
from graphql.core.execution import execute
57
from graphql.core.type import (
@@ -14,6 +16,7 @@
1416
GraphQLEnumType,
1517
GraphQLEnumValue,
1618
)
19+
from graphql.core.validation.rules import ProvidedNonNullArguments
1720

1821
introspection_query = '''
1922
query IntrospectionQuery {
@@ -707,10 +710,16 @@ def test_fails_as_expected_on_the_type_root_field_without_an_arg():
707710
'testField': GraphQLField(GraphQLString)
708711
})
709712
schema = GraphQLSchema(TestType)
710-
request = '{ __type { name } }'
713+
request = '''
714+
{
715+
__type {
716+
name
717+
}
718+
}'''
711719
result = graphql(schema, request)
712-
# TODO: change after implementing validation
713-
assert not result.errors
720+
expected_error = {'message': ProvidedNonNullArguments.missing_field_arg_message('__type', 'name', 'String!'),
721+
'locations': [SourceLocation(line=3, column=9)]}
722+
assert (expected_error in [format_error(error) for error in result.errors])
714723

715724

716725
def test_exposes_descriptions_on_types_and_fields():
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import ProvidedNonNullArguments
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def missing_field_arg(field_name, arg_name, type_name, line, column):
7+
return {
8+
'message': ProvidedNonNullArguments.missing_field_arg_message(field_name, arg_name, type_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def missing_directive_arg(directive_name, arg_name, type_name, line, column):
14+
return {
15+
'message': ProvidedNonNullArguments.missing_directive_arg_message(directive_name, arg_name, type_name),
16+
'locations': [SourceLocation(line, column)]
17+
}
18+
19+
20+
def test_ignores_unknown_arguments():
21+
expect_passes_rule(ProvidedNonNullArguments, '''
22+
{
23+
dog {
24+
isHousetrained(unknownArgument: true)
25+
}
26+
}''')
27+
28+
29+
def test_no_arg_on_optional_arg():
30+
expect_passes_rule(ProvidedNonNullArguments, '''
31+
{
32+
dog {
33+
isHousetrained
34+
}
35+
}''')
36+
37+
38+
def test_multiple_args():
39+
expect_passes_rule(ProvidedNonNullArguments, '''
40+
{
41+
complicatedArgs {
42+
multipleReqs(req1: 1, req2: 2)
43+
}
44+
}
45+
''')
46+
47+
48+
def test_multiple_args_reverse_order():
49+
expect_passes_rule(ProvidedNonNullArguments, '''
50+
{
51+
complicatedArgs {
52+
multipleReqs(req2: 2, req1: 1)
53+
}
54+
}
55+
''')
56+
57+
58+
def test_no_args_on_multiple_optional():
59+
expect_passes_rule(ProvidedNonNullArguments, '''
60+
{
61+
complicatedArgs {
62+
multipleOpts
63+
}
64+
}
65+
''')
66+
67+
68+
def test_one_arg_on_multiple_optional():
69+
expect_passes_rule(ProvidedNonNullArguments, '''
70+
{
71+
complicatedArgs {
72+
multipleOpts(opt1: 1)
73+
}
74+
}
75+
''')
76+
77+
78+
def test_second_arg_on_multiple_optional():
79+
expect_passes_rule(ProvidedNonNullArguments, '''
80+
{
81+
complicatedArgs {
82+
multipleOpts(opt2: 1)
83+
}
84+
}
85+
''')
86+
87+
88+
def test_multiple_reqs_on_mixed_list():
89+
expect_passes_rule(ProvidedNonNullArguments, '''
90+
{
91+
complicatedArgs {
92+
multipleOptAndReq(req1: 3, req2: 4)
93+
}
94+
}
95+
''')
96+
97+
98+
def test_multiple_reqs_and_one_opt_on_mixed_list():
99+
expect_passes_rule(ProvidedNonNullArguments, '''
100+
{
101+
complicatedArgs {
102+
multipleOptAndReq(req1: 3, req2: 4, opt1: 5)
103+
}
104+
}
105+
''')
106+
107+
108+
def test_all_reqs_and_opts_on_mixed_list():
109+
expect_passes_rule(ProvidedNonNullArguments, '''
110+
{
111+
complicatedArgs {
112+
multipleOptAndReq(req1: 3, req2: 4, opt1: 5, opt2: 6)
113+
}
114+
}
115+
''')
116+
117+
118+
def test_missing_one_non_nullable_argument():
119+
expect_fails_rule(ProvidedNonNullArguments, '''
120+
{
121+
complicatedArgs {
122+
multipleReqs(req2: 2)
123+
}
124+
}
125+
''', [
126+
missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13)
127+
])
128+
129+
130+
def test_missing_multiple_non_nullable_arguments():
131+
expect_fails_rule(ProvidedNonNullArguments, '''
132+
{
133+
complicatedArgs {
134+
multipleReqs
135+
}
136+
}
137+
''', [
138+
missing_field_arg('multipleReqs', 'req1', 'Int!', 4, 13),
139+
missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13)
140+
])
141+
142+
143+
def test_incorrect_value_and_missing_argument():
144+
expect_fails_rule(ProvidedNonNullArguments, '''
145+
{
146+
complicatedArgs {
147+
multipleReqs(req1: "one")
148+
}
149+
}
150+
''', [
151+
missing_field_arg('multipleReqs', 'req2', 'Int!', 4, 13)
152+
])
153+
154+
155+
def test_ignore_unknown_directives():
156+
expect_passes_rule(ProvidedNonNullArguments, '''
157+
{
158+
dog @unknown
159+
}
160+
''')
161+
162+
163+
def test_with_directives_of_valid_type():
164+
expect_passes_rule(ProvidedNonNullArguments, '''
165+
{
166+
dog @include(if: true) {
167+
name
168+
}
169+
human @skip(if: false) {
170+
name
171+
}
172+
}
173+
''')
174+
175+
176+
def test_with_directive_with_missing_types():
177+
expect_fails_rule(ProvidedNonNullArguments, '''
178+
{
179+
dog @include {
180+
name @skip
181+
}
182+
}
183+
''', [
184+
missing_directive_arg('include', 'if', 'Boolean!', 3, 13),
185+
missing_directive_arg('skip', 'if', 'Boolean!', 4, 18),
186+
])

tests/core_validation/utils.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
GraphQLField,
77
GraphQLArgument,
88
GraphQLID,
9+
GraphQLNonNull,
910
GraphQLString,
11+
GraphQLInt,
12+
GraphQLFloat,
1013
GraphQLBoolean,
1114
GraphQLInterfaceType,
1215
GraphQLEnumType,
@@ -64,8 +67,46 @@
6467
})
6568

6669
ComplicatedArgs = GraphQLObjectType('ComplicatedArgs', {
70+
'intArgField': GraphQLField(GraphQLString, {
71+
'intArg': GraphQLArgument(GraphQLInt)
72+
}),
73+
'nonNullIntArgField': GraphQLField(GraphQLString, {
74+
'nonNullIntArg': GraphQLArgument(GraphQLNonNull(GraphQLInt))
75+
}),
76+
'stringArgField': GraphQLField(GraphQLString, {
77+
'stringArg': GraphQLArgument(GraphQLString)
78+
}),
79+
'booleanArgField': GraphQLField(GraphQLString, {
80+
'booleanArg': GraphQLArgument(GraphQLBoolean)
81+
}),
82+
'enumArgField': GraphQLField(GraphQLString, {
83+
'enumArg': GraphQLArgument(FurColor)
84+
}),
85+
'floatArgField': GraphQLField(GraphQLString, {
86+
'floatArg': GraphQLArgument(GraphQLFloat)
87+
}),
88+
'idArgField': GraphQLField(GraphQLString, {
89+
'idArg': GraphQLArgument(GraphQLID)
90+
}),
91+
'stringListArgField': GraphQLField(GraphQLString, {
92+
'stringListArg': GraphQLArgument(GraphQLList(GraphQLString))
93+
}),
6794
'complexArgField': GraphQLField(GraphQLString, {
68-
'complexArg': GraphQLArgument(ComplexInput),
95+
'complexArg': GraphQLArgument(ComplexInput)
96+
}),
97+
'multipleReqs': GraphQLField(GraphQLString, {
98+
'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
99+
'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
100+
}),
101+
'multipleOpts': GraphQLField(GraphQLString, {
102+
'opt1': GraphQLArgument(GraphQLInt, 0),
103+
'opt2': GraphQLArgument(GraphQLInt, 0)
104+
}),
105+
'multipleOptsAndReq': GraphQLField(GraphQLString, {
106+
'req1': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
107+
'req2': GraphQLArgument(GraphQLNonNull(GraphQLInt)),
108+
'opt1': GraphQLArgument(GraphQLInt, 0),
109+
'opt2': GraphQLArgument(GraphQLInt, 0)
69110
})
70111
})
71112

0 commit comments

Comments
 (0)