Skip to content

Commit 9937a7a

Browse files
feat!: Adds SDL validation context
1 parent ad736da commit 9937a7a

File tree

2 files changed

+310
-229
lines changed

2 files changed

+310
-229
lines changed

Sources/GraphQL/Validation/Validate.swift

Lines changed: 23 additions & 229 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,29 @@ public func validate(
5252
return errors
5353
}
5454

55+
/**
56+
* @internal
57+
*/
58+
func validateSDL(
59+
documentAST: Document,
60+
schemaToExtend: GraphQLSchema? = nil,
61+
rules: [SDLValidationRule] = specifiedSDLRules
62+
) -> [GraphQLError] {
63+
var errors: [GraphQLError] = []
64+
let context = SDLValidationContext(
65+
ast: documentAST,
66+
schema: schemaToExtend
67+
) { error in
68+
errors.append(error)
69+
}
70+
71+
let visitors = rules.map { rule in
72+
rule(context)
73+
}
74+
visit(root: documentAST, visitor: visitInParallel(visitors: visitors))
75+
return errors
76+
}
77+
5578
/**
5679
* This uses a specialized visitor which runs multiple visitors in parallel,
5780
* while maintaining the visitor skip and break API.
@@ -73,232 +96,3 @@ func visit(
7396
)
7497
return context.errors
7598
}
76-
77-
public enum HasSelectionSet {
78-
case operation(OperationDefinition)
79-
case fragment(FragmentDefinition)
80-
81-
public var node: Node {
82-
switch self {
83-
case let .operation(operation):
84-
return operation
85-
case let .fragment(fragment):
86-
return fragment
87-
}
88-
}
89-
}
90-
91-
extension HasSelectionSet: Hashable {
92-
public func hash(into hasher: inout Hasher) {
93-
switch self {
94-
case let .operation(operation):
95-
return hasher.combine(operation.hashValue)
96-
case let .fragment(fragment):
97-
return hasher.combine(fragment.hashValue)
98-
}
99-
}
100-
101-
public static func == (lhs: HasSelectionSet, rhs: HasSelectionSet) -> Bool {
102-
switch (lhs, rhs) {
103-
case let (.operation(l), .operation(r)):
104-
return l == r
105-
case let (.fragment(l), .fragment(r)):
106-
return l == r
107-
default:
108-
return false
109-
}
110-
}
111-
}
112-
113-
public typealias VariableUsage = (node: Variable, type: GraphQLInputType?, defaultValue: Map?)
114-
115-
/**
116-
* An instance of this class is passed as the "this" context to all validators,
117-
* allowing access to commonly useful contextual information from within a
118-
* validation rule.
119-
*/
120-
public final class ValidationContext {
121-
public let schema: GraphQLSchema
122-
let ast: Document
123-
let typeInfo: TypeInfo
124-
var errors: [GraphQLError]
125-
var fragments: [String: FragmentDefinition]
126-
var fragmentSpreads: [SelectionSet: [FragmentSpread]]
127-
var recursivelyReferencedFragments: [OperationDefinition: [FragmentDefinition]]
128-
var variableUsages: [HasSelectionSet: [VariableUsage]]
129-
var recursiveVariableUsages: [OperationDefinition: [VariableUsage]]
130-
131-
init(schema: GraphQLSchema, ast: Document, typeInfo: TypeInfo) {
132-
self.schema = schema
133-
self.ast = ast
134-
self.typeInfo = typeInfo
135-
errors = []
136-
fragments = [:]
137-
fragmentSpreads = [:]
138-
recursivelyReferencedFragments = [:]
139-
variableUsages = [:]
140-
recursiveVariableUsages = [:]
141-
}
142-
143-
public func report(error: GraphQLError) {
144-
errors.append(error)
145-
}
146-
147-
public func getFragment(name: String) -> FragmentDefinition? {
148-
var fragments = self.fragments
149-
150-
if fragments.isEmpty {
151-
fragments = ast.definitions.reduce([:]) { frags, statement in
152-
var frags = frags
153-
154-
if let statement = statement as? FragmentDefinition {
155-
frags[statement.name.value] = statement
156-
}
157-
158-
return frags
159-
}
160-
161-
self.fragments = fragments
162-
}
163-
164-
return fragments[name]
165-
}
166-
167-
public func getFragmentSpreads(node: SelectionSet) -> [FragmentSpread] {
168-
// Uncommenting this creates unpredictably wrong fragment path matching.
169-
// Failures can be seen in NoFragmentCyclesRuleTests.testNoSpreadingItselfDeeplyTwoPaths
170-
// if let spreads = fragmentSpreads[node] {
171-
// return spreads
172-
// }
173-
174-
var spreads = [FragmentSpread]()
175-
var setsToVisit: [SelectionSet] = [node]
176-
177-
while let set = setsToVisit.popLast() {
178-
for selection in set.selections {
179-
if let selection = selection as? FragmentSpread {
180-
spreads.append(selection)
181-
} else if let selection = selection as? InlineFragment {
182-
setsToVisit.append(selection.selectionSet)
183-
} else if
184-
let selection = selection as? Field,
185-
let selectionSet = selection.selectionSet
186-
{
187-
setsToVisit.append(selectionSet)
188-
}
189-
}
190-
}
191-
192-
// fragmentSpreads[node] = spreads
193-
return spreads
194-
}
195-
196-
public func getRecursivelyReferencedFragments(operation: OperationDefinition)
197-
-> [FragmentDefinition]
198-
{
199-
if let fragments = recursivelyReferencedFragments[operation] {
200-
return fragments
201-
}
202-
203-
var fragments = [FragmentDefinition]()
204-
var collectedNames: [String: Bool] = [:]
205-
var nodesToVisit: [SelectionSet] = [operation.selectionSet]
206-
207-
while let node = nodesToVisit.popLast() {
208-
let spreads = getFragmentSpreads(node: node)
209-
210-
for spread in spreads {
211-
let fragName = spread.name.value
212-
if collectedNames[fragName] != true {
213-
collectedNames[fragName] = true
214-
if let fragment = getFragment(name: fragName) {
215-
fragments.append(fragment)
216-
nodesToVisit.append(fragment.selectionSet)
217-
}
218-
}
219-
}
220-
}
221-
222-
recursivelyReferencedFragments[operation] = fragments
223-
return fragments
224-
}
225-
226-
public func getVariableUsages(node: HasSelectionSet) -> [VariableUsage] {
227-
if let usages = variableUsages[node] {
228-
return usages
229-
}
230-
231-
var usages = [VariableUsage]()
232-
let typeInfo = TypeInfo(schema: schema)
233-
234-
visit(
235-
root: node.node,
236-
visitor: visitWithTypeInfo(
237-
typeInfo: typeInfo,
238-
visitor: Visitor(enter: { node, _, _, _, _ in
239-
if node is VariableDefinition {
240-
return .skip
241-
}
242-
243-
if let variable = node as? Variable {
244-
usages.append(VariableUsage(
245-
node: variable,
246-
type: typeInfo.inputType,
247-
defaultValue: typeInfo.defaultValue
248-
))
249-
}
250-
251-
return .continue
252-
})
253-
)
254-
)
255-
256-
variableUsages[node] = usages
257-
return usages
258-
}
259-
260-
public func getRecursiveVariableUsages(operation: OperationDefinition) -> [VariableUsage] {
261-
if let usages = recursiveVariableUsages[operation] {
262-
return usages
263-
}
264-
265-
var usages = getVariableUsages(node: .operation(operation))
266-
let fragments = getRecursivelyReferencedFragments(operation: operation)
267-
268-
for fragment in fragments {
269-
let newUsages = getVariableUsages(node: .fragment(fragment))
270-
usages.append(contentsOf: newUsages)
271-
}
272-
273-
recursiveVariableUsages[operation] = usages
274-
return usages
275-
}
276-
277-
public var type: GraphQLOutputType? {
278-
return typeInfo.type
279-
}
280-
281-
public var parentType: GraphQLCompositeType? {
282-
return typeInfo.parentType
283-
}
284-
285-
public var inputType: GraphQLInputType? {
286-
return typeInfo.inputType
287-
}
288-
289-
public var parentInputType: GraphQLInputType? {
290-
return typeInfo.parentInputType
291-
}
292-
293-
public var fieldDef: GraphQLFieldDefinition? {
294-
return typeInfo.fieldDef
295-
}
296-
297-
public var directive: GraphQLDirective? {
298-
return typeInfo.directive
299-
}
300-
301-
public var argument: GraphQLArgumentDefinition? {
302-
return typeInfo.argument
303-
}
304-
}

0 commit comments

Comments
 (0)