Skip to content

Commit 073718b

Browse files
feature: Adds FragmentsOnCompositeTypesRule
1 parent 9ab0dfe commit 073718b

File tree

3 files changed

+197
-1
lines changed

3 files changed

+197
-1
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
2+
/**
3+
* Fragments on composite type
4+
*
5+
* Fragments use a type condition to determine if they apply, since fragments
6+
* can only be spread into a composite type (object, interface, or union), the
7+
* type condition must also be a composite type.
8+
*
9+
* See https://spec.graphql.org/draft/#sec-Fragments-On-Composite-Types
10+
*/
11+
func FragmentsOnCompositeTypesRule(context: ValidationContext) -> Visitor {
12+
return Visitor(
13+
enter: { node, _, _, _, _ in
14+
if let fragment = node as? InlineFragment {
15+
if let typeCondition = fragment.typeCondition {
16+
if let type = typeFromAST(schema: context.schema, inputTypeAST: typeCondition) {
17+
if type is GraphQLCompositeType {
18+
return .continue
19+
}
20+
let typeStr = typeCondition.name.value
21+
context.report(
22+
error: GraphQLError(
23+
message:
24+
"Fragment cannot condition on non composite type \"\(typeStr)\".",
25+
nodes: [typeCondition]
26+
)
27+
)
28+
}
29+
}
30+
return .continue
31+
}
32+
if let fragment = node as? FragmentDefinition {
33+
let typeCondition = fragment.typeCondition
34+
if let type = typeFromAST(schema: context.schema, inputTypeAST: typeCondition) {
35+
if type is GraphQLCompositeType {
36+
return .continue
37+
}
38+
let typeStr = typeCondition.name.value
39+
context.report(
40+
error: GraphQLError(
41+
message:
42+
"Fragment \"\(fragment.name.value)\" cannot condition on non composite type \"\(typeStr)\".",
43+
nodes: [typeCondition]
44+
)
45+
)
46+
}
47+
return .continue
48+
}
49+
return .continue
50+
}
51+
)
52+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ public let specifiedRules: [(ValidationContext) -> Visitor] = [
77
LoneAnonymousOperationRule,
88
// SingleFieldSubscriptionsRule,
99
KnownTypeNamesRule,
10-
// FragmentsOnCompositeTypesRule,
10+
FragmentsOnCompositeTypesRule,
1111
// VariablesAreInputTypesRule,
1212
ScalarLeafsRule,
1313
FieldsOnCorrectTypeRule,
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
@testable import GraphQL
2+
import XCTest
3+
4+
class FragmentsOnCompositeTypesRuleTests: ValidationTestCase {
5+
override func setUp() {
6+
rule = FragmentsOnCompositeTypesRule
7+
}
8+
9+
func testObjectIsValidFragmentType() throws {
10+
try assertValid(
11+
"""
12+
fragment validFragment on Dog {
13+
barks
14+
}
15+
"""
16+
)
17+
}
18+
19+
func testInterfaceIsValidFragmentType() throws {
20+
try assertValid(
21+
"""
22+
fragment validFragment on Pet {
23+
name
24+
}
25+
"""
26+
)
27+
}
28+
29+
func testObjectIsValidInlineFragmentType() throws {
30+
try assertValid(
31+
"""
32+
fragment validFragment on Pet {
33+
... on Dog {
34+
barks
35+
}
36+
}
37+
"""
38+
)
39+
}
40+
41+
func testInterfaceIsValidInlineFragmentType() throws {
42+
try assertValid(
43+
"""
44+
fragment validFragment on Mammal {
45+
... on Canine {
46+
name
47+
}
48+
}
49+
"""
50+
)
51+
}
52+
53+
func testInlineFragmentWithoutTypeIsValid() throws {
54+
try assertValid(
55+
"""
56+
fragment validFragment on Pet {
57+
... {
58+
name
59+
}
60+
}
61+
"""
62+
)
63+
}
64+
65+
func testUnionIsValidFragmentType() throws {
66+
try assertValid(
67+
"""
68+
fragment validFragment on CatOrDog {
69+
__typename
70+
}
71+
"""
72+
)
73+
}
74+
75+
func testScalarIsInvalidFragmentType() throws {
76+
let errors = try assertInvalid(
77+
errorCount: 1,
78+
query:
79+
"""
80+
fragment scalarFragment on Boolean {
81+
bad
82+
}
83+
"""
84+
)
85+
try assertValidationError(
86+
error: errors[0],
87+
locations: [(line: 1, column: 28)],
88+
message: "Fragment \"scalarFragment\" cannot condition on non composite type \"Boolean\"."
89+
)
90+
}
91+
92+
func testEnumIsInvalidFragmentType() throws {
93+
let errors = try assertInvalid(
94+
errorCount: 1,
95+
query:
96+
"""
97+
fragment scalarFragment on FurColor {
98+
bad
99+
}
100+
"""
101+
)
102+
try assertValidationError(
103+
error: errors[0],
104+
locations: [(line: 1, column: 28)],
105+
message: "Fragment \"scalarFragment\" cannot condition on non composite type \"FurColor\"."
106+
)
107+
}
108+
109+
func testInputObjectIsInvalidFragmentType() throws {
110+
let errors = try assertInvalid(
111+
errorCount: 1,
112+
query:
113+
"""
114+
fragment inputFragment on ComplexInput {
115+
stringField
116+
}
117+
"""
118+
)
119+
try assertValidationError(
120+
error: errors[0],
121+
locations: [(line: 1, column: 27)],
122+
message: "Fragment \"inputFragment\" cannot condition on non composite type \"ComplexInput\"."
123+
)
124+
}
125+
126+
func testScalarIsInvalidInlineFragmentType() throws {
127+
let errors = try assertInvalid(
128+
errorCount: 1,
129+
query:
130+
"""
131+
fragment invalidFragment on Pet {
132+
... on String {
133+
barks
134+
}
135+
}
136+
"""
137+
)
138+
try assertValidationError(
139+
error: errors[0],
140+
locations: [(line: 2, column: 10)],
141+
message: "Fragment cannot condition on non composite type \"String\"."
142+
)
143+
}
144+
}

0 commit comments

Comments
 (0)