Skip to content

Commit 70ed2b6

Browse files
feat: Adds PossibleTypeExtensionsRule
1 parent 44fb127 commit 70ed2b6

File tree

3 files changed

+464
-1
lines changed

3 files changed

+464
-1
lines changed
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
2+
/**
3+
* Possible type extension
4+
*
5+
* A type extension is only valid if the type is defined and has the same kind.
6+
*/
7+
func PossibleTypeExtensionsRule(
8+
context: SDLValidationContext
9+
) -> Visitor {
10+
let schema = context.getSchema()
11+
var definedTypes = [String: TypeDefinition]()
12+
13+
for def in context.getDocument().definitions {
14+
if let def = def as? TypeDefinition {
15+
definedTypes[def.name.value] = def
16+
}
17+
}
18+
19+
return Visitor(
20+
enter: { node, _, _, _, _ in
21+
if let node = node as? ScalarExtensionDefinition {
22+
checkExtension(node: node)
23+
} else if let node = node as? TypeExtensionDefinition {
24+
checkExtension(node: node)
25+
} else if let node = node as? InterfaceExtensionDefinition {
26+
checkExtension(node: node)
27+
} else if let node = node as? UnionExtensionDefinition {
28+
checkExtension(node: node)
29+
} else if let node = node as? EnumExtensionDefinition {
30+
checkExtension(node: node)
31+
} else if let node = node as? InputObjectExtensionDefinition {
32+
checkExtension(node: node)
33+
}
34+
return .continue
35+
}
36+
)
37+
38+
func checkExtension(node: TypeExtension) {
39+
let typeName = node.name.value
40+
let defNode = definedTypes[typeName]
41+
let existingType = schema?.getType(name: typeName)
42+
43+
var expectedKind: Kind? = nil
44+
if let defNode = defNode {
45+
expectedKind = defKindToExtKind[defNode.kind]
46+
} else if let existingType = existingType {
47+
expectedKind = typeToExtKind(type: existingType)
48+
}
49+
50+
if let expectedKind = expectedKind {
51+
if expectedKind != node.kind {
52+
let kindStr = extensionKindToTypeName(kind: node.kind)
53+
var nodes: [any Node] = []
54+
if let defNode = defNode {
55+
nodes.append(defNode)
56+
}
57+
nodes.append(node)
58+
context.report(
59+
error: GraphQLError(
60+
message: "Cannot extend non-\(kindStr) type \"\(typeName)\".",
61+
nodes: nodes
62+
)
63+
)
64+
}
65+
} else {
66+
var allTypeNames = Array(definedTypes.keys)
67+
allTypeNames.append(contentsOf: schema?.typeMap.keys ?? [])
68+
69+
context.report(
70+
error: GraphQLError(
71+
message: "Cannot extend type \"\(typeName)\" because it is not defined." +
72+
didYouMean(suggestions: suggestionList(
73+
input: typeName,
74+
options: allTypeNames
75+
)),
76+
nodes: [node.name]
77+
)
78+
)
79+
}
80+
}
81+
}
82+
83+
let defKindToExtKind: [Kind: Kind] = [
84+
.scalarTypeDefinition: .scalarExtensionDefinition,
85+
.objectTypeDefinition: .typeExtensionDefinition,
86+
.interfaceTypeDefinition: .interfaceExtensionDefinition,
87+
.unionTypeDefinition: .unionExtensionDefinition,
88+
.enumTypeDefinition: .enumExtensionDefinition,
89+
.inputObjectTypeDefinition: .inputObjectExtensionDefinition,
90+
]
91+
92+
func typeToExtKind(type: GraphQLNamedType) -> Kind {
93+
if type is GraphQLScalarType {
94+
return .scalarExtensionDefinition
95+
}
96+
if type is GraphQLObjectType {
97+
return .typeExtensionDefinition
98+
}
99+
if type is GraphQLInterfaceType {
100+
return .interfaceExtensionDefinition
101+
}
102+
if type is GraphQLUnionType {
103+
return .unionExtensionDefinition
104+
}
105+
if type is GraphQLEnumType {
106+
return .enumExtensionDefinition
107+
}
108+
if type is GraphQLInputObjectType {
109+
return .inputObjectExtensionDefinition
110+
}
111+
// Not reachable. All possible types have been considered
112+
fatalError("Unexpected type: \(type)")
113+
}
114+
115+
func extensionKindToTypeName(kind: Kind) -> String {
116+
switch kind {
117+
case .scalarExtensionDefinition:
118+
return "scalar"
119+
case .typeExtensionDefinition:
120+
return "object"
121+
case .interfaceExtensionDefinition:
122+
return "interface"
123+
case .unionExtensionDefinition:
124+
return "union"
125+
case .enumExtensionDefinition:
126+
return "enum"
127+
case .inputObjectExtensionDefinition:
128+
return "input object"
129+
// Not reachable. All possible types have been considered
130+
default:
131+
fatalError("Unexpected kind: \(kind)")
132+
}
133+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public let specifiedSDLRules: [SDLValidationRule] = [
4747
KnownTypeNamesRule,
4848
KnownDirectivesRule,
4949
UniqueDirectivesPerLocationRule,
50-
// PossibleTypeExtensionsRule,
50+
PossibleTypeExtensionsRule,
5151
// KnownArgumentNamesOnDirectivesRule,
5252
UniqueArgumentNamesRule,
5353
UniqueInputFieldNamesRule,

0 commit comments

Comments
 (0)