Skip to content

Commit acb7186

Browse files
committed
KnownTypeNames, LoneAnonymousOperation rules
1 parent f59e425 commit acb7186

File tree

5 files changed

+162
-2
lines changed

5 files changed

+162
-2
lines changed

graphql/core/validation/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
specified_rules = [
99
Rules.UniqueOperationNames,
10+
Rules.LoneAnonymousOperation,
11+
Rules.KnownTypeNames,
1012
]
1113

1214

graphql/core/validation/rules.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ..language.ast import OperationDefinition
12
from ..error import GraphQLError
23
from ..language.visitor import Visitor
34

@@ -25,3 +26,36 @@ def enter_OperationDefinition(self, node, *args):
2526
@staticmethod
2627
def message(operation_name):
2728
return 'There can only be one operation named "{}".'.format(operation_name)
29+
30+
31+
class LoneAnonymousOperation(ValidationRule):
32+
def __init__(self, context):
33+
super(LoneAnonymousOperation, self).__init__(context)
34+
self._op_count = 0
35+
36+
def enter_Document(self, node, *args):
37+
n = 0
38+
for definition in node.definitions:
39+
if isinstance(definition, OperationDefinition):
40+
n += 1
41+
self._op_count = n
42+
43+
def enter_OperationDefinition(self, node, *args):
44+
if not node.name and self._op_count > 1:
45+
return GraphQLError(self.message(), [node])
46+
47+
@staticmethod
48+
def message():
49+
return 'This anonymous operation must be the only defined operation.'
50+
51+
52+
class KnownTypeNames(ValidationRule):
53+
def enter_NamedType(self, node, *args):
54+
type_name = node.name.value
55+
type = self.context.get_schema().get_type(type_name)
56+
if not type:
57+
return GraphQLError(self.message(type_name), [node])
58+
59+
@staticmethod
60+
def message(type):
61+
return 'Unknown type "{}".'.format(type)
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import KnownTypeNames
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def unknown_type(type_name, line, column):
7+
return {
8+
'message': KnownTypeNames.message(type_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def test_known_type_names_are_valid():
14+
expect_passes_rule(KnownTypeNames, '''
15+
query Foo($var: String, $required: [String!]!) {
16+
user(id: 4) {
17+
pets { ... on Pet { name }, ...PetFields }
18+
}
19+
}
20+
fragment PetFields on Pet {
21+
name
22+
}
23+
''')
24+
25+
26+
def test_unknown_type_names_are_invalid():
27+
expect_fails_rule(KnownTypeNames, '''
28+
query Foo($var: JumbledUpLetters) {
29+
user(id: 4) {
30+
name
31+
pets { ... on Badger { name }, ...PetFields }
32+
}
33+
}
34+
fragment PetFields on Peettt {
35+
name
36+
}
37+
''', [
38+
unknown_type('JumbledUpLetters', 2, 23),
39+
unknown_type('Badger', 5, 25),
40+
unknown_type('Peettt', 8, 29),
41+
])
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import LoneAnonymousOperation
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def anon_not_alone(line, column):
7+
return {
8+
'message': LoneAnonymousOperation.message(),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def test_no_operations():
14+
expect_passes_rule(LoneAnonymousOperation, '''
15+
fragment fragA on Type {
16+
field
17+
}
18+
''')
19+
20+
21+
def test_one_anon_operation():
22+
expect_passes_rule(LoneAnonymousOperation, '''
23+
{
24+
field
25+
}
26+
''')
27+
28+
29+
def test_multiple_named_operation():
30+
expect_passes_rule(LoneAnonymousOperation, '''
31+
query Foo {
32+
field
33+
}
34+
35+
query Bar {
36+
field
37+
}
38+
''')
39+
40+
41+
def test_anon_operation_with_fragment():
42+
expect_passes_rule(LoneAnonymousOperation, '''
43+
{
44+
...Foo
45+
}
46+
fragment Foo on Type {
47+
field
48+
}
49+
''')
50+
51+
52+
def test_multiple_anon_operations():
53+
expect_fails_rule(LoneAnonymousOperation, '''
54+
{
55+
fieldA
56+
}
57+
{
58+
fieldB
59+
}
60+
''', [
61+
anon_not_alone(2, 7),
62+
anon_not_alone(5, 7),
63+
])
64+
65+
66+
def test_anon_operation_with_another_operation():
67+
expect_fails_rule(LoneAnonymousOperation, '''
68+
{
69+
fieldA
70+
}
71+
mutation Foo {
72+
fieldB
73+
}
74+
''', [anon_not_alone(2, 7)])

tests/core_validation/utils.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@
66
GraphQLField,
77
GraphQLArgument,
88
GraphQLID,
9-
GraphQLString, GraphQLBoolean)
9+
GraphQLString,
10+
GraphQLBoolean,
11+
GraphQLInterfaceType)
1012
from graphql.core.error import format_error
1113

14+
Pet = GraphQLInterfaceType('Pet', {
15+
'name': GraphQLField(GraphQLString, {
16+
'surname': GraphQLArgument(GraphQLBoolean),
17+
}),
18+
})
19+
1220
Human = GraphQLObjectType('Human', {
1321
'name': GraphQLField(GraphQLString, {
1422
'surname': GraphQLArgument(GraphQLBoolean),
@@ -18,7 +26,8 @@
1826
QueryRoot = GraphQLObjectType('QueryRoot', {
1927
'human': GraphQLField(Human, {
2028
'id': GraphQLArgument(GraphQLID)
21-
})
29+
}),
30+
'pet': GraphQLField(Pet),
2231
})
2332

2433
default_schema = GraphQLSchema(query=QueryRoot)

0 commit comments

Comments
 (0)