Skip to content

Commit f7bb35a

Browse files
feature: Adds ValuesOfCorrectTypeRule
1 parent e13ef31 commit f7bb35a

File tree

6 files changed

+1638
-3
lines changed

6 files changed

+1638
-3
lines changed

Sources/GraphQL/Type/Definition.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1379,6 +1379,10 @@ extension InputObjectFieldDefinition: KeySubscriptable {
13791379
}
13801380
}
13811381

1382+
public func isRequiredInputField(_ field: InputObjectFieldDefinition) -> Bool {
1383+
return field.type is GraphQLNonNull && field.defaultValue == nil
1384+
}
1385+
13821386
public typealias InputObjectFieldDefinitionMap = [String: InputObjectFieldDefinition]
13831387

13841388
/**

Sources/GraphQL/Utilities/TypeInfo.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ final class TypeInfo {
4747
return nil
4848
}
4949

50+
var parentInputType: GraphQLInputType? {
51+
if inputTypeStack.count >= 2 {
52+
return inputTypeStack[inputTypeStack.count - 2]
53+
}
54+
return nil
55+
}
56+
5057
var fieldDef: GraphQLFieldDefinition? {
5158
if !fieldDefStack.isEmpty {
5259
return fieldDefStack[fieldDefStack.count - 1]
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
2+
/**
3+
* Value literals of correct type
4+
*
5+
* A GraphQL document is only valid if all value literals are of the type
6+
* expected at their position.
7+
*
8+
* See https://spec.graphql.org/draft/#sec-Values-of-Correct-Type
9+
*/
10+
func ValuesOfCorrectTypeRule(context: ValidationContext) -> Visitor {
11+
var variableDefinitions = [String: VariableDefinition]()
12+
13+
return Visitor(
14+
enter: { node, _, _, _, _ in
15+
if node is OperationDefinition {
16+
variableDefinitions = [:]
17+
return .continue
18+
}
19+
if let variableDefinition = node as? VariableDefinition {
20+
variableDefinitions[variableDefinition.variable.name.value] = variableDefinition
21+
return .continue
22+
}
23+
if let list = node as? ListValue {
24+
guard let type = getNullableType(type: context.parentInputType) else {
25+
return .continue
26+
}
27+
guard type is GraphQLList else {
28+
isValidValueNode(context, list)
29+
return .break // Don't traverse further.
30+
}
31+
return .continue
32+
}
33+
if let object = node as? ObjectValue {
34+
let type = getNamedType(type: context.inputType)
35+
guard let type = type as? GraphQLInputObjectType else {
36+
isValidValueNode(context, object)
37+
return .break // Don't traverse further.
38+
}
39+
// Ensure every required field exists.
40+
let fieldNodeMap = Dictionary(grouping: object.fields) { field in
41+
field.name.value
42+
}
43+
for (fieldName, fieldDef) in type.fields {
44+
if fieldNodeMap[fieldName] == nil, isRequiredInputField(fieldDef) {
45+
let typeStr = fieldDef.type
46+
context.report(
47+
error: GraphQLError(
48+
message: "Field \"\(type.name).\(fieldDef.name)\" of required type \"\(typeStr)\" was not provided.",
49+
nodes: [object]
50+
)
51+
)
52+
}
53+
}
54+
55+
// TODO: Add oneOf support
56+
return .continue
57+
}
58+
if let field = node as? ObjectField {
59+
let parentType = getNamedType(type: context.parentInputType)
60+
if
61+
context.inputType == nil,
62+
let parentType = parentType as? GraphQLInputObjectType
63+
{
64+
let suggestions = suggestionList(
65+
input: field.name.value,
66+
options: Array(parentType.fields.keys)
67+
)
68+
context.report(
69+
error: GraphQLError(
70+
message:
71+
"Field \"\(field.name.value)\" is not defined by type \"\(parentType.name)\"." +
72+
didYouMean(suggestions: suggestions),
73+
nodes: [field]
74+
)
75+
)
76+
}
77+
return .continue
78+
}
79+
if let null = node as? NullValue {
80+
let type = context.inputType
81+
if let type = type as? GraphQLNonNull {
82+
context.report(
83+
error: GraphQLError(
84+
message:
85+
"Expected value of type \"\(type)\", found \(print(ast: node)).",
86+
nodes: [null]
87+
)
88+
)
89+
}
90+
return .continue
91+
}
92+
if let node = node as? EnumValue {
93+
isValidValueNode(context, node)
94+
return .continue
95+
}
96+
if let node = node as? IntValue {
97+
isValidValueNode(context, node)
98+
return .continue
99+
}
100+
if let node = node as? FloatValue {
101+
isValidValueNode(context, node)
102+
return .continue
103+
}
104+
if let node = node as? StringValue {
105+
isValidValueNode(context, node)
106+
return .continue
107+
}
108+
if let node = node as? BooleanValue {
109+
isValidValueNode(context, node)
110+
return .continue
111+
}
112+
return .continue
113+
}
114+
)
115+
}
116+
117+
/**
118+
* Any value literal may be a valid representation of a Scalar, depending on
119+
* that scalar type.
120+
*/
121+
func isValidValueNode(_ context: ValidationContext, _ node: Value) {
122+
// Report any error at the full type expected by the location.
123+
guard let locationType = context.inputType else {
124+
return
125+
}
126+
127+
let type = getNamedType(type: locationType)
128+
129+
if !isLeafType(type: type) {
130+
context.report(
131+
error: GraphQLError(
132+
message: "Expected value of type \"\(locationType)\", found \(print(ast: node)).",
133+
nodes: [node]
134+
)
135+
)
136+
return
137+
}
138+
139+
// Scalars and Enums determine if a literal value is valid via parseLiteral(),
140+
// which may throw or return an invalid value to indicate failure.
141+
do {
142+
if let type = type as? GraphQLScalarType {
143+
if try type.parseLiteral(valueAST: node) == .undefined {
144+
context.report(
145+
error: GraphQLError(
146+
message: "Expected value of type \"\(locationType)\", found \(print(ast: node)).",
147+
nodes: [node]
148+
)
149+
)
150+
}
151+
}
152+
if let type = type as? GraphQLEnumType {
153+
if try type.parseLiteral(valueAST: node) == .undefined {
154+
context.report(
155+
error: GraphQLError(
156+
message: "Expected value of type \"\(locationType)\", found \(print(ast: node)).",
157+
nodes: [node]
158+
)
159+
)
160+
}
161+
}
162+
} catch {
163+
if let graphQLError = error as? GraphQLError {
164+
context.report(error: graphQLError)
165+
} else {
166+
context.report(
167+
error: GraphQLError(
168+
message: "Expected value of type \"\(locationType)\", found \(print(ast: node)).",
169+
nodes: [node]
170+
)
171+
)
172+
}
173+
}
174+
}

Sources/GraphQL/Validation/SpecifiedRules.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,9 @@ public let specifiedRules: [(ValidationContext) -> Visitor] = [
2626
// DeferStreamDirectiveLabelRule,
2727
KnownArgumentNamesRule,
2828
UniqueArgumentNamesRule,
29-
// ArgumentsOfCorrectTypeRule,
30-
// ValuesOfCorrectTypeRule,
29+
ValuesOfCorrectTypeRule,
3130
// ProvidedRequiredArgumentsRule,
3231
ProvidedNonNullArgumentsRule,
33-
// DefaultValuesOfCorrectTypeRule,
3432
// VariablesInAllowedPositionRule,
3533
// OverlappingFieldsCanBeMergedRule,
3634
UniqueInputFieldNamesRule,

Sources/GraphQL/Validation/Validate.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,10 @@ public final class ValidationContext {
285285
return typeInfo.inputType
286286
}
287287

288+
public var parentInputType: GraphQLInputType? {
289+
return typeInfo.parentInputType
290+
}
291+
288292
public var fieldDef: GraphQLFieldDefinition? {
289293
return typeInfo.fieldDef
290294
}

0 commit comments

Comments
 (0)