Skip to content

Commit 6db07e7

Browse files
committed
FragmentsOnCompositeType rule
1 parent d8fe99d commit 6db07e7

File tree

6 files changed

+145
-4
lines changed

6 files changed

+145
-4
lines changed

graphql/core/language/source.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ class Source(object):
55
def __init__(self, body, name='GraphQL'):
66
self.body = body
77
self.name = name
8+
9+
def __eq__(self, other):
10+
if isinstance(other, Source):
11+
return self.body == other.body and self.name == other.name
12+
return False

graphql/core/language/visitor.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ def visit(root, visitor, key_map=None):
101101

102102
result = None
103103
if not isinstance(node, list):
104-
assert is_node(node), 'Invalid AST Node: ' + node
104+
assert is_node(node), 'Invalid AST Node: ' + repr(node)
105105
if is_leaving:
106106
result = visitor.leave(node, key, parent, path, ancestors)
107107
else:
@@ -146,7 +146,7 @@ def visit(root, visitor, key_map=None):
146146

147147

148148
def is_node(maybe_node):
149-
return isinstance(maybe_node, object) # FIXME
149+
return hasattr(maybe_node, 'loc') # FIXME
150150

151151

152152
class Visitor(object):

graphql/core/validation/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
Rules.UniqueOperationNames,
1010
Rules.LoneAnonymousOperation,
1111
Rules.KnownTypeNames,
12+
Rules.FragmentsOnCompositeTypes,
1213
]
1314

1415

graphql/core/validation/rules.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
from ..language.ast import OperationDefinition
21
from ..error import GraphQLError
2+
from ..type.definition import is_composite_type
3+
from ..language.ast import OperationDefinition
34
from ..language.visitor import Visitor
5+
from ..language.printer import print_ast
46

57

68
class ValidationRule(Visitor):
@@ -59,3 +61,29 @@ def enter_NamedType(self, node, *args):
5961
@staticmethod
6062
def message(type):
6163
return 'Unknown type "{}".'.format(type)
64+
65+
66+
class FragmentsOnCompositeTypes(ValidationRule):
67+
def enter_InlineFragment(self, node, *args):
68+
type = self.context.get_type()
69+
if type and not is_composite_type(type):
70+
return GraphQLError(
71+
self.inline_message(print_ast(node.type_condition)),
72+
[node.type_condition]
73+
)
74+
75+
def enter_FragmentDefinition(self, node, *args):
76+
type = self.context.get_type()
77+
if type and not is_composite_type(type):
78+
return GraphQLError(
79+
self.message(node.name.value, print_ast(node.type_condition)),
80+
[node.type_condition]
81+
)
82+
83+
@staticmethod
84+
def inline_message(type):
85+
return 'Fragment cannot condition on non composite type "{}".'.format(type)
86+
87+
@staticmethod
88+
def message(frag_name, type):
89+
return 'Fragment "{}" cannot condition on non composite type "{}".'.format(frag_name, type)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from graphql.core.language.location import SourceLocation
2+
from graphql.core.validation.rules import FragmentsOnCompositeTypes
3+
from utils import expect_passes_rule, expect_fails_rule
4+
5+
6+
def error(frag_name, type_name, line, column):
7+
return {
8+
'message': FragmentsOnCompositeTypes.message(frag_name, type_name),
9+
'locations': [SourceLocation(line, column)]
10+
}
11+
12+
13+
def test_object_is_valid_fragment_type():
14+
expect_passes_rule(FragmentsOnCompositeTypes, '''
15+
fragment validFragment on Dog {
16+
barks
17+
}
18+
''')
19+
20+
21+
def test_interface_is_valid_fragment_type():
22+
expect_passes_rule(FragmentsOnCompositeTypes, '''
23+
fragment validFragment on Pet {
24+
name
25+
}
26+
''')
27+
28+
29+
def test_object_is_valid_inline_fragment_type():
30+
expect_passes_rule(FragmentsOnCompositeTypes, '''
31+
fragment validFragment on Pet {
32+
... on Dog {
33+
barks
34+
}
35+
}
36+
''')
37+
38+
39+
def test_union_is_valid_fragment_type():
40+
expect_passes_rule(FragmentsOnCompositeTypes, '''
41+
fragment validFragment on CatOrDog {
42+
__typename
43+
}
44+
''')
45+
46+
47+
def test_scalar_is_invalid_fragment_type():
48+
expect_fails_rule(FragmentsOnCompositeTypes, '''
49+
fragment scalarFragment on Boolean {
50+
bad
51+
}
52+
''', [error('scalarFragment', 'Boolean', 2, 34)])
53+
54+
55+
def test_enum_is_invalid_fragment_type():
56+
expect_fails_rule(FragmentsOnCompositeTypes, '''
57+
fragment scalarFragment on FurColor {
58+
bad
59+
}
60+
''', [error('scalarFragment', 'FurColor', 2, 34)])
61+
62+
63+
def test_input_object_is_invalid_fragment_type():
64+
expect_fails_rule(FragmentsOnCompositeTypes, '''
65+
fragment inputFragment on ComplexInput {
66+
stringField
67+
}
68+
''', [error('inputFragment', 'ComplexInput', 2, 33)])
69+
70+
71+
def test_scalar_is_invalid_inline_fragment_type():
72+
expect_fails_rule(FragmentsOnCompositeTypes, '''
73+
fragment invalidFragment on Pet {
74+
... on String {
75+
barks
76+
}
77+
}
78+
''', [{
79+
'message': FragmentsOnCompositeTypes.inline_message('String'),
80+
'locations': [SourceLocation(3, 16)]
81+
}])

tests/core_validation/utils.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
GraphQLID,
99
GraphQLString,
1010
GraphQLBoolean,
11-
GraphQLInterfaceType)
11+
GraphQLInterfaceType, GraphQLEnumType, GraphQLEnumValue, GraphQLInputObjectType)
1212
from graphql.core.error import format_error
1313

1414
Pet = GraphQLInterfaceType('Pet', {
@@ -17,17 +17,43 @@
1717
}),
1818
})
1919

20+
Dog = GraphQLObjectType('Dog', {
21+
'barks': GraphQLField(GraphQLBoolean),
22+
}, interfaces=[Pet])
23+
24+
Cat = GraphQLObjectType('Cat', lambda: {
25+
'furColor': GraphQLField(FurColor)
26+
}, interfaces=[Pet])
27+
2028
Human = GraphQLObjectType('Human', {
2129
'name': GraphQLField(GraphQLString, {
2230
'surname': GraphQLArgument(GraphQLBoolean),
2331
})
2432
})
2533

34+
FurColor = GraphQLEnumType('FurColor', {
35+
'BROWN': GraphQLEnumValue(0),
36+
'BLACK': GraphQLEnumValue(1),
37+
'TAN': GraphQLEnumValue(2),
38+
'SPOTTED': GraphQLEnumValue(3),
39+
})
40+
41+
ComplexInput = GraphQLInputObjectType('ComplexInput', {
42+
'stringField': GraphQLField(GraphQLString)
43+
})
44+
45+
ComplicatedArgs = GraphQLObjectType('ComplicatedArgs', {
46+
'complexArgField': GraphQLField(GraphQLString, {
47+
'complexArg': GraphQLArgument(ComplexInput),
48+
})
49+
})
50+
2651
QueryRoot = GraphQLObjectType('QueryRoot', {
2752
'human': GraphQLField(Human, {
2853
'id': GraphQLArgument(GraphQLID)
2954
}),
3055
'pet': GraphQLField(Pet),
56+
'complicatedArgs': GraphQLField(ComplicatedArgs),
3157
})
3258

3359
default_schema = GraphQLSchema(query=QueryRoot)

0 commit comments

Comments
 (0)