Skip to content

Commit 312c571

Browse files
williambaileypaulofaria
authored andcommitted
Introduce the FieldExecutionStrategy protocol (#14)
* Introduce the FieldExecutionStrategy protocol * Make `resolveField` public so that it can be used by others
1 parent f0be0fa commit 312c571

File tree

4 files changed

+138
-116
lines changed

4 files changed

+138
-116
lines changed

Sources/GraphQL/Execution/Execute.swift

Lines changed: 82 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,23 @@
2424
* Namely, schema of the type system that is currently executing,
2525
* and the fragments defined in the query document
2626
*/
27-
final class ExecutionContext {
28-
let schema: GraphQLSchema
29-
let fragments: [String: FragmentDefinition]
30-
let rootValue: Any
31-
let contextValue: Any
32-
let operation: OperationDefinition
33-
let variableValues: [String: Map]
34-
var errors: [GraphQLError]
27+
public final class ExecutionContext {
28+
29+
let queryStrategy: FieldExecutionStrategy
30+
let mutationStrategy: FieldExecutionStrategy
31+
let subscriptionStrategy: FieldExecutionStrategy
32+
let schema: GraphQLSchema
33+
let fragments: [String: FragmentDefinition]
34+
let rootValue: Any
35+
let contextValue: Any
36+
let operation: OperationDefinition
37+
let variableValues: [String: Map]
38+
var errors: [GraphQLError]
3539

3640
init(
41+
queryStrategy: FieldExecutionStrategy,
42+
mutationStrategy: FieldExecutionStrategy,
43+
subscriptionStrategy: FieldExecutionStrategy,
3744
schema: GraphQLSchema,
3845
fragments: [String: FragmentDefinition],
3946
rootValue: Any,
@@ -42,6 +49,9 @@ final class ExecutionContext {
4249
variableValues: [String: Map],
4350
errors: [GraphQLError]
4451
) {
52+
self.queryStrategy = queryStrategy
53+
self.mutationStrategy = mutationStrategy
54+
self.subscriptionStrategy = subscriptionStrategy
4555
self.schema = schema
4656
self.fragments = fragments
4757
self.rootValue = rootValue
@@ -53,13 +63,56 @@ final class ExecutionContext {
5363
}
5464
}
5565

66+
public protocol FieldExecutionStrategy {
67+
func executeFields(
68+
exeContext: ExecutionContext,
69+
parentType: GraphQLObjectType,
70+
sourceValue: Any,
71+
path: [IndexPathElement],
72+
fields: [String: [Field]]
73+
) throws -> [String: Any]
74+
}
75+
76+
/**
77+
* Serial field execution strategy that's suitable for the "Evaluating selection sets" section of the spec for "write" mode.
78+
*/
79+
public struct SerialFieldExecutionStrategy: FieldExecutionStrategy {
80+
public func executeFields(
81+
exeContext: ExecutionContext,
82+
parentType: GraphQLObjectType,
83+
sourceValue: Any,
84+
path: [IndexPathElement],
85+
fields: [String: [Field]]
86+
) throws -> [String: Any] {
87+
return try fields.reduce([:]) { results, field in
88+
var results = results
89+
let fieldASTs = field.value
90+
let fieldPath = path + [field.key] as [IndexPathElement]
91+
92+
let result = try resolveField(
93+
exeContext: exeContext,
94+
parentType: parentType,
95+
source: sourceValue,
96+
fieldASTs: fieldASTs,
97+
path: fieldPath
98+
)
99+
100+
results[field.key] = result ?? Map.null
101+
return results
102+
}
103+
}
104+
}
105+
56106
/**
57107
* Implements the "Evaluating requests" section of the GraphQL specification.
58108
*
59109
* If the arguments to this func do not result in a legal execution context,
60110
* a GraphQLError will be thrown immediately explaining the invalid input.
61111
*/
62112
func execute(
113+
queryStrategy: FieldExecutionStrategy,
114+
mutationStrategy: FieldExecutionStrategy,
115+
subscriptionStrategy: FieldExecutionStrategy,
63116
schema: GraphQLSchema,
64117
documentAST: Document,
65118
rootValue: Any,
@@ -70,6 +123,9 @@ func execute(
70123
// If a valid context cannot be created due to incorrect arguments,
71124
// this will throw an error.
72125
let context = try buildExecutionContext(
126+
queryStrategy: queryStrategy,
127+
mutationStrategy: mutationStrategy,
128+
subscriptionStrategy: subscriptionStrategy,
73129
schema: schema,
74130
documentAST: documentAST,
75131
rootValue: rootValue,
@@ -110,6 +166,9 @@ func execute(
110166
* Throws a GraphQLError if a valid execution context cannot be created.
111167
*/
112168
func buildExecutionContext(
169+
queryStrategy: FieldExecutionStrategy,
170+
mutationStrategy: FieldExecutionStrategy,
171+
subscriptionStrategy: FieldExecutionStrategy,
113172
schema: GraphQLSchema,
114173
documentAST: Document,
115174
rootValue: Any,
@@ -160,6 +219,9 @@ func buildExecutionContext(
160219
)
161220

162221
return ExecutionContext(
222+
queryStrategy: queryStrategy,
223+
mutationStrategy: mutationStrategy,
224+
subscriptionStrategy: subscriptionStrategy,
163225
schema: schema,
164226
fragments: fragments,
165227
rootValue: rootValue,
@@ -193,17 +255,17 @@ func executeOperation(
193255

194256
let path: [IndexPathElement] = []
195257

196-
if operation.operation == .mutation {
197-
return try executeFieldsSerially(
198-
exeContext: exeContext,
199-
parentType: type,
200-
sourceValue: rootValue,
201-
path: path,
202-
fields: fields
203-
)
258+
let fieldExecutionStrategy: FieldExecutionStrategy
259+
switch operation.operation {
260+
case .query:
261+
fieldExecutionStrategy = exeContext.queryStrategy
262+
case .mutation:
263+
fieldExecutionStrategy = exeContext.mutationStrategy
264+
case .subscription:
265+
fieldExecutionStrategy = exeContext.subscriptionStrategy
204266
}
205267

206-
return try executeFields(
268+
return try fieldExecutionStrategy.executeFields(
207269
exeContext: exeContext,
208270
parentType: type,
209271
sourceValue: rootValue,
@@ -243,55 +305,6 @@ func getOperationRootType(
243305
}
244306
}
245307

246-
/**
247-
* Implements the "Evaluating selection sets" section of the spec
248-
* for "write" mode.
249-
*/
250-
func executeFieldsSerially(
251-
exeContext: ExecutionContext,
252-
parentType: GraphQLObjectType,
253-
sourceValue: Any,
254-
path: [IndexPathElement],
255-
fields: [String: [Field]]
256-
) throws -> [String: Any] {
257-
return try fields.reduce([:]) { results, field in
258-
var results = results
259-
let fieldASTs = field.value
260-
let fieldPath = path + [field.key] as [IndexPathElement]
261-
262-
let result = try resolveField(
263-
exeContext: exeContext,
264-
parentType: parentType,
265-
source: sourceValue,
266-
fieldASTs: fieldASTs,
267-
path: fieldPath
268-
)
269-
270-
results[field.key] = result ?? Map.null
271-
return results
272-
}
273-
}
274-
275-
/**
276-
* Implements the "Evaluating selection sets" section of the spec
277-
* for "read" mode.
278-
*/
279-
func executeFields(
280-
exeContext: ExecutionContext,
281-
parentType: GraphQLObjectType,
282-
sourceValue: Any,
283-
path: [IndexPathElement],
284-
fields: [String: [Field]]
285-
) throws -> [String : Any] {
286-
return try executeFieldsSerially(
287-
exeContext: exeContext,
288-
parentType: parentType,
289-
sourceValue: sourceValue,
290-
path: path,
291-
fields: fields
292-
)
293-
}
294-
295308
/**
296309
* Given a selectionSet, adds all of the fields in that selection to
297310
* the passed in map of fields, and returns it at the end.
@@ -467,7 +480,7 @@ func getFieldEntryKey(node: Field) -> String {
467480
* then calls completeValue to complete promises, serialize scalars, or execute
468481
* the sub-selection-set for objects.
469482
*/
470-
func resolveField(
483+
public func resolveField(
471484
exeContext: ExecutionContext,
472485
parentType: GraphQLObjectType,
473486
source: Any,
@@ -911,8 +924,8 @@ func completeObjectValue(
911924
)
912925
}
913926
}
914-
915-
return try executeFields(
927+
928+
return try exeContext.queryStrategy.executeFields(
916929
exeContext: exeContext,
917930
parentType: returnType,
918931
sourceValue: result,

Sources/GraphQL/GraphQL.swift

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@
66
/// may wish to separate the validation and execution phases to a static time
77
/// tooling step, and a server runtime step.
88
///
9-
/// - parameter schema: The GraphQL type system to use when validating and executing a query.
10-
/// - parameter request: A GraphQL language formatted string representing the requested operation.
11-
/// - parameter rootValue: The value provided as the first argument to resolver functions on the top level type (e.g. the query object type).
12-
/// - parameter contextValue: A context value provided to all resolver functions functions
13-
/// - parameter variableValues: A mapping of variable name to runtime value to use for all variables defined in the `request`.
14-
/// - parameter operationName: The name of the operation to use if `request` contains multiple possible operations. Can be omitted if `request` contains only one operation.
9+
/// - parameter queryStrategy: The field execution strategy to use for query requests
10+
/// - parameter mutationStrategy: The field execution strategy to use for mutation requests
11+
/// - parameter subscriptionStrategy: The field execution strategy to use for subscription requests
12+
/// - parameter schema: The GraphQL type system to use when validating and executing a query.
13+
/// - parameter request: A GraphQL language formatted string representing the requested operation.
14+
/// - parameter rootValue: The value provided as the first argument to resolver functions on the top level type (e.g. the query object type).
15+
/// - parameter contextValue: A context value provided to all resolver functions functions
16+
/// - parameter variableValues: A mapping of variable name to runtime value to use for all variables defined in the `request`.
17+
/// - parameter operationName: The name of the operation to use if `request` contains multiple possible operations. Can be omitted if `request` contains only one operation.
1518
///
1619
/// - throws: throws GraphQLError if an error occurs while parsing the `request`.
1720
///
1821
/// - returns: returns a `Map` dictionary containing the result of the query inside the key `data` and any validation or execution errors inside the key `errors`. The value of `data` might be `null` if, for example, the query is invalid. It's possible to have both `data` and `errors` if an error occurs only in a specific field. If that happens the value of that field will be `null` and there will be an error inside `errors` specifying the reason for the failure and the path of the failed field.
1922
public func graphql(
23+
queryStrategy: FieldExecutionStrategy = SerialFieldExecutionStrategy(),
24+
mutationStrategy: FieldExecutionStrategy = SerialFieldExecutionStrategy(),
25+
subscriptionStrategy: FieldExecutionStrategy = SerialFieldExecutionStrategy(),
2026
schema: GraphQLSchema,
2127
request: String,
2228
rootValue: Any = Void(),
@@ -33,6 +39,9 @@ public func graphql(
3339
}
3440

3541
return try execute(
42+
queryStrategy: queryStrategy,
43+
mutationStrategy: mutationStrategy,
44+
subscriptionStrategy: subscriptionStrategy,
3645
schema: schema,
3746
documentAST: documentAST,
3847
rootValue: rootValue,

0 commit comments

Comments
 (0)