Skip to content

Commit 2087058

Browse files
committed
Merge branch 'default-values-of-correct-type'
Conflicts: .gitignore
2 parents 0d7e0c3 + 6f73df7 commit 2087058

File tree

5 files changed

+168
-5
lines changed

5 files changed

+168
-5
lines changed

.gitignore

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,5 +60,4 @@ docs/_build/
6060
target/
6161

6262
# IntelliJ
63-
.idea
64-
63+
.idea

graphql/core/utils.py

Lines changed: 45 additions & 0 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,
@@ -167,3 +169,46 @@ def get_field_def(schema, parent_type, field_ast):
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: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from ..utils import type_from_ast
1+
from ..utils import type_from_ast, is_valid_literal_value
22
from ..error import GraphQLError
33
from ..type.definition import is_composite_type, is_input_type, is_leaf_type, GraphQLNonNull
44
from ..language import ast
@@ -349,7 +349,30 @@ def missing_directive_arg_message(name, arg_name, type):
349349

350350

351351
class DefaultValuesOfCorrectType(ValidationRule):
352-
pass
352+
def enter_VariableDefinition(self, node, *args):
353+
name = node.variable.name.value
354+
default_value = node.default_value
355+
type = self.context.get_input_type()
356+
if isinstance(type, GraphQLNonNull) and default_value:
357+
return GraphQLError(
358+
self.default_for_non_null_arg_message(name, type, type.of_type),
359+
[default_value]
360+
)
361+
362+
if type and default_value and not is_valid_literal_value(type, default_value):
363+
return GraphQLError(
364+
self.bad_value_for_default_arg_message(name, type, print_ast(default_value)),
365+
[default_value]
366+
)
367+
368+
@staticmethod
369+
def default_for_non_null_arg_message(var_name, type, guess_type):
370+
return 'Variable "${}" of type "{}" is required and will not use the default value. ' \
371+
'Perhaps you meant to use type "{}".'.format(var_name, type, guess_type)
372+
373+
@staticmethod
374+
def bad_value_for_default_arg_message(var_name, type, value):
375+
return 'Variable "${}" of type "{}" has invalid default value: {}.'.format(var_name, type, value)
353376

354377

355378
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: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,11 @@
6363
})
6464

6565
ComplexInput = GraphQLInputObjectType('ComplexInput', {
66-
'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)),
6771
})
6872

6973
ComplicatedArgs = GraphQLObjectType('ComplicatedArgs', {

0 commit comments

Comments
 (0)