Skip to content

Commit f9f5965

Browse files
feat: Adds UniqueFieldDefinitionNamesRule
1 parent 6d14a15 commit f9f5965

File tree

3 files changed

+557
-1
lines changed

3 files changed

+557
-1
lines changed
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
2+
/**
3+
* Unique field definition names
4+
*
5+
* A GraphQL complex type is only valid if all its fields are uniquely named.
6+
*/
7+
func UniqueFieldDefinitionNamesRule(
8+
context: SDLValidationContext
9+
) -> Visitor {
10+
let schema = context.getSchema()
11+
let existingTypeMap = schema?.typeMap ?? [:]
12+
var knownFieldNames = [String: [String: Name]]()
13+
14+
return Visitor(
15+
enter: { node, _, _, _, _ in
16+
if let node = node as? InputObjectTypeDefinition {
17+
checkFieldUniqueness(name: node.name, fields: node.fields)
18+
} else if let node = node as? InputObjectExtensionDefinition {
19+
checkFieldUniqueness(name: node.name, fields: node.definition.fields)
20+
} else if let node = node as? InterfaceTypeDefinition {
21+
checkFieldUniqueness(name: node.name, fields: node.fields)
22+
} else if let node = node as? InterfaceExtensionDefinition {
23+
checkFieldUniqueness(name: node.name, fields: node.definition.fields)
24+
} else if let node = node as? ObjectTypeDefinition {
25+
checkFieldUniqueness(name: node.name, fields: node.fields)
26+
} else if let node = node as? TypeExtensionDefinition {
27+
checkFieldUniqueness(name: node.name, fields: node.definition.fields)
28+
}
29+
return .continue
30+
}
31+
)
32+
33+
func checkFieldUniqueness(
34+
name: Name,
35+
fields: [FieldDefinition]
36+
) {
37+
let typeName = name.value
38+
var fieldNames = knownFieldNames[typeName] ?? [String: Name]()
39+
let fieldNodes = fields
40+
for fieldDef in fieldNodes {
41+
let fieldName = fieldDef.name.value
42+
if
43+
let existingType = existingTypeMap[typeName],
44+
hasField(type: existingType, fieldName: fieldName)
45+
{
46+
context.report(
47+
error: GraphQLError(
48+
message: "Field \"\(typeName).\(fieldName)\" already exists in the schema. It cannot also be defined in this type extension.",
49+
nodes: [fieldDef.name]
50+
)
51+
)
52+
continue
53+
}
54+
if let knownFieldName = fieldNames[fieldName] {
55+
context.report(
56+
error: GraphQLError(
57+
message: "Field \"\(typeName).\(fieldName)\" can only be defined once.",
58+
nodes: [knownFieldName, fieldDef.name]
59+
)
60+
)
61+
} else {
62+
fieldNames[fieldName] = fieldDef.name
63+
}
64+
}
65+
knownFieldNames[typeName] = fieldNames
66+
}
67+
68+
func checkFieldUniqueness(
69+
name: Name,
70+
fields: [InputValueDefinition]
71+
) {
72+
let typeName = name.value
73+
var fieldNames = knownFieldNames[typeName] ?? [String: Name]()
74+
let fieldNodes = fields
75+
for fieldDef in fieldNodes {
76+
let fieldName = fieldDef.name.value
77+
if
78+
let existingType = existingTypeMap[typeName],
79+
hasField(type: existingType, fieldName: fieldName)
80+
{
81+
context.report(
82+
error: GraphQLError(
83+
message: "Field \"\(typeName).\(fieldName)\" already exists in the schema. It cannot also be defined in this type extension.",
84+
nodes: [fieldDef.name]
85+
)
86+
)
87+
continue
88+
}
89+
if let knownFieldName = fieldNames[fieldName] {
90+
context.report(
91+
error: GraphQLError(
92+
message: "Field \"\(typeName).\(fieldName)\" can only be defined once.",
93+
nodes: [knownFieldName, fieldDef.name]
94+
)
95+
)
96+
} else {
97+
fieldNames[fieldName] = fieldDef.name
98+
}
99+
}
100+
knownFieldNames[typeName] = fieldNames
101+
}
102+
}
103+
104+
func hasField(type: GraphQLNamedType, fieldName: String) -> Bool {
105+
if let type = type as? GraphQLObjectType {
106+
return (try? type.getFields()[fieldName]) != nil
107+
} else if let type = type as? GraphQLInterfaceType {
108+
return (try? type.getFields()[fieldName]) != nil
109+
} else if let type = type as? GraphQLInputObjectType {
110+
return (try? type.getFields()[fieldName]) != nil
111+
}
112+
return false
113+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public let specifiedSDLRules: [SDLValidationRule] = [
4141
UniqueOperationTypesRule,
4242
UniqueTypeNamesRule,
4343
UniqueEnumValueNamesRule,
44-
// UniqueFieldDefinitionNamesRule,
44+
UniqueFieldDefinitionNamesRule,
4545
// UniqueArgumentDefinitionNamesRule,
4646
// UniqueDirectiveNamesRule,
4747
KnownTypeNamesRule,

0 commit comments

Comments
 (0)