Skip to content

Commit 19d955f

Browse files
committed
implement validation DefaultValuesOfCorrectType
* Implement missing schema in test harness. * Implement is_valid_literal_value * Implement unit tests for DefaultValuesOfCorrectType #6
1 parent 1f0c725 commit 19d955f

File tree

5 files changed

+215
-8
lines changed

5 files changed

+215
-8
lines changed

.gitignore

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

62+
# IntelliJ
63+
.idea

graphql/core/utils.py

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
GraphQLUnionType,
77
GraphQLList,
88
GraphQLNonNull,
9+
GraphQLScalarType,
10+
GraphQLEnumType,
911
get_named_type,
1012
get_nullable_type,
1113
is_composite_type,
@@ -160,10 +162,53 @@ def get_field_def(schema, parent_type, field_ast):
160162
return TypeMetaFieldDef
161163
elif name == TypeNameMetaFieldDef.name and \
162164
isinstance(parent_type, (
163-
GraphQLObjectType,
164-
GraphQLInterfaceType,
165-
GraphQLUnionType,
165+
GraphQLObjectType,
166+
GraphQLInterfaceType,
167+
GraphQLUnionType,
166168
)):
167169
return TypeNameMetaFieldDef
168170
elif isinstance(parent_type, (GraphQLObjectType, GraphQLInterfaceType)):
169171
return parent_type.get_fields().get(name)
172+
173+
174+
def is_valid_literal_value(type, value_ast):
175+
if isinstance(type, GraphQLNonNull):
176+
if not value_ast:
177+
return False
178+
179+
of_type = type.of_type
180+
return is_valid_literal_value(of_type, value_ast)
181+
182+
if not value_ast:
183+
return True
184+
185+
if isinstance(value_ast, ast.Variable):
186+
return True
187+
188+
if isinstance(type, GraphQLList):
189+
item_type = type.of_type
190+
if isinstance(value_ast, ast.ListValue):
191+
return all(is_valid_literal_value(item_type, item_ast) for item_ast in value_ast.values)
192+
193+
return is_valid_literal_value(item_type, value_ast)
194+
195+
if isinstance(type, GraphQLInputObjectType):
196+
if not isinstance(value_ast, ast.ObjectValue):
197+
return False
198+
199+
fields = type.get_fields()
200+
field_asts = value_ast.fields
201+
202+
if any(not fields.get(field_ast.name.value, None) for field_ast in field_asts):
203+
return False
204+
205+
field_ast_map = {field_ast.name.value: field_ast for field_ast in field_asts}
206+
get_field_ast_value = lambda field_name: field_ast_map[
207+
field_name].value if field_name in field_ast_map else None
208+
209+
return all(is_valid_literal_value(field.type, get_field_ast_value(field_name))
210+
for field_name, field in fields.items())
211+
212+
assert isinstance(type, (GraphQLScalarType, GraphQLEnumType)), 'Must be input type'
213+
214+
return not is_nullish(type.coerce_literal(value_ast))

graphql/core/validation/rules.py

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
from ..utils import type_from_ast
1+
from ..utils import type_from_ast, is_valid_literal_value
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
@@ -304,7 +304,30 @@ class ProvidedNonNullArguments(ValidationRule):
304304

305305

306306
class DefaultValuesOfCorrectType(ValidationRule):
307-
pass
307+
def enter_VariableDefinition(self, node, *args):
308+
name = node.variable.name.value
309+
default_value = node.default_value
310+
type = self.context.get_input_type()
311+
if isinstance(type, GraphQLNonNull) and default_value:
312+
return GraphQLError(
313+
self.default_for_non_null_arg_message(name, type, type.of_type),
314+
[default_value]
315+
)
316+
317+
if type and default_value and not is_valid_literal_value(type, default_value):
318+
return GraphQLError(
319+
self.bad_value_for_default_arg_message(name, type, print_ast(default_value)),
320+
[default_value]
321+
)
322+
323+
@staticmethod
324+
def default_for_non_null_arg_message(var_name, type, guess_type):
325+
return 'Variable "${}" of type "{}" is required and will not use the default value. ' \
326+
'Perhaps you meant to use type "{}".'.format(var_name, type, guess_type)
327+
328+
@staticmethod
329+
def bad_value_for_default_arg_message(var_name, type, value):
330+
return 'Variable "${}" of type "{}" has invalid default value: {}.'.format(var_name, type, value)
308331

309332

310333
class VariablesInAllowedPosition(ValidationRule):
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import DefaultValuesOfCorrectType
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def default_for_non_null_arg(var_name, type_name, guess_type_name, line, column):
7+
return {
8+
'message': DefaultValuesOfCorrectType.default_for_non_null_arg_message(var_name, type_name, guess_type_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def bad_value(var_name, type_name, value, line, column):
14+
return {
15+
'message': DefaultValuesOfCorrectType.bad_value_for_default_arg_message(var_name, type_name, value),
16+
'locations': [SourceLocation(line, column)]
17+
}
18+
19+
20+
def test_variables_with_no_default_values():
21+
return expect_passes_rule(DefaultValuesOfCorrectType, '''
22+
query NullableValues($a: Int, $b: String, $c: ComplexInput) {
23+
dog { name }
24+
}
25+
''')
26+
27+
28+
def test_required_variables_without_default_values():
29+
expect_passes_rule(DefaultValuesOfCorrectType, '''
30+
query RequiredValues($a: Int!, $b: String!) {
31+
dog { name }
32+
}
33+
''')
34+
35+
36+
def test_variables_with_valid_default_values():
37+
expect_passes_rule(DefaultValuesOfCorrectType, '''
38+
query WithDefaultValues(
39+
$a: Int = 1,
40+
$b: String = "ok",
41+
$c: ComplexInput = { requiredField: true, intField: 3 }
42+
) {
43+
dog { name }
44+
}
45+
''')
46+
47+
48+
def test_no_required_variables_with_default_values():
49+
expect_fails_rule(DefaultValuesOfCorrectType, '''
50+
query UnreachableDefaultValues($a: Int! = 3, $b: String! = "default") {
51+
dog { name }
52+
}
53+
''', [
54+
default_for_non_null_arg('a', 'Int!', 'Int', 2, 47),
55+
default_for_non_null_arg('b', 'String!', 'String', 2, 64)
56+
])
57+
58+
59+
def test_variables_with_invalid_default_values():
60+
expect_fails_rule(DefaultValuesOfCorrectType, '''
61+
query InvalidDefaultValues(
62+
$a: Int = "one",
63+
$b: String = 4,
64+
$c: ComplexInput = "notverycomplex"
65+
) {
66+
dog { name }
67+
}
68+
''', [
69+
bad_value('a', 'Int', '"one"', 3, 19),
70+
bad_value('b', 'String', '4', 4, 22),
71+
bad_value('c', 'ComplexInput', '"notverycomplex"', 5, 28)
72+
])
73+
74+
75+
def test_variables_missing_required_field():
76+
expect_fails_rule(DefaultValuesOfCorrectType, '''
77+
query MissingRequiredField($a: ComplexInput = {intField: 3}) {
78+
dog { name }
79+
}
80+
''', [
81+
bad_value('a', 'ComplexInput', '{intField: 3}', 2, 51)
82+
])
83+
84+
85+
def test_list_variables_with_invalid_item():
86+
expect_fails_rule(DefaultValuesOfCorrectType, '''
87+
query invalidItem($a: [String] = ["one", 2]) {
88+
dog { name }
89+
}
90+
''', [
91+
bad_value('a', '[String]', '["one", 2]', 2, 38)
92+
])

tests/core_validation/utils.py

Lines changed: 47 additions & 2 deletions
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,
@@ -60,12 +63,54 @@
6063
})
6164

6265
ComplexInput = GraphQLInputObjectType('ComplexInput', {
63-
'stringField': GraphQLField(GraphQLString)
66+
'requiredField': GraphQLField(GraphQLNonNull(GraphQLBoolean)),
67+
'intField': GraphQLField(GraphQLInt),
68+
'stringField': GraphQLField(GraphQLString),
69+
'booleanField': GraphQLField(GraphQLBoolean),
70+
'stringListField': GraphQLField(GraphQLList(GraphQLString)),
6471
})
6572

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

0 commit comments

Comments
 (0)