forked from GraphQLSwift/GraphQL
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSubscribe.swift
More file actions
290 lines (273 loc) · 11.3 KB
/
Copy pathSubscribe.swift
File metadata and controls
290 lines (273 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
import NIO
import OrderedCollections
/**
* Implements the "Subscribe" algorithm described in the GraphQL specification.
*
* Returns a future which resolves to a SubscriptionResult containing either
* a SubscriptionObservable (if successful), or GraphQLErrors (error).
*
* If the client-provided arguments to this function do not result in a
* compliant subscription, the future will resolve to a
* SubscriptionResult containing `errors` and no `observable`.
*
* If the source stream could not be created due to faulty subscription
* resolver logic or underlying systems, the future will resolve to a
* SubscriptionResult containing `errors` and no `observable`.
*
* If the operation succeeded, the future will resolve to a SubscriptionResult,
* containing an `observable` which yields a stream of GraphQLResults
* representing the response stream.
*
* Accepts either an object with named arguments, or individual arguments.
*/
func subscribe(
queryStrategy: QueryFieldExecutionStrategy,
mutationStrategy: MutationFieldExecutionStrategy,
subscriptionStrategy: SubscriptionFieldExecutionStrategy,
instrumentation: Instrumentation,
schema: GraphQLSchema,
documentAST: Document,
rootValue: Any,
context: Any,
eventLoopGroup: EventLoopGroup,
variableValues: [String: Map] = [:],
operationName: String? = nil
) -> EventLoopFuture<SubscriptionResult> {
let sourceFuture = createSourceEventStream(
queryStrategy: queryStrategy,
mutationStrategy: mutationStrategy,
subscriptionStrategy: subscriptionStrategy,
instrumentation: instrumentation,
schema: schema,
documentAST: documentAST,
rootValue: rootValue,
context: context,
eventLoopGroup: eventLoopGroup,
variableValues: variableValues,
operationName: operationName
)
return sourceFuture.map { sourceResult -> SubscriptionResult in
if let sourceStream = sourceResult.stream {
let subscriptionStream = sourceStream.map { eventPayload -> Future<GraphQLResult> in
// For each payload yielded from a subscription, map it over the normal
// GraphQL `execute` function, with `payload` as the rootValue.
// This implements the "MapSourceToResponseEvent" algorithm described in
// the GraphQL specification. The `execute` function provides the
// "ExecuteSubscriptionEvent" algorithm, as it is nearly identical to the
// "ExecuteQuery" algorithm, for which `execute` is also used.
execute(
queryStrategy: queryStrategy,
mutationStrategy: mutationStrategy,
subscriptionStrategy: subscriptionStrategy,
instrumentation: instrumentation,
schema: schema,
documentAST: documentAST,
rootValue: eventPayload,
context: context,
eventLoopGroup: eventLoopGroup,
variableValues: variableValues,
operationName: operationName
)
}
return SubscriptionResult(stream: subscriptionStream, errors: sourceResult.errors)
} else {
return SubscriptionResult(errors: sourceResult.errors)
}
}
}
/**
* Implements the "CreateSourceEventStream" algorithm described in the
* GraphQL specification, resolving the subscription source event stream.
*
* Returns a Future which resolves to a SourceEventStreamResult, containing
* either an Observable (if successful) or GraphQLErrors (error).
*
* If the client-provided arguments to this function do not result in a
* compliant subscription, the future will resolve to a
* SourceEventStreamResult containing `errors` and no `observable`.
*
* If the source stream could not be created due to faulty subscription
* resolver logic or underlying systems, the future will resolve to a
* SourceEventStreamResult containing `errors` and no `observable`.
*
* If the operation succeeded, the future will resolve to a SubscriptionResult,
* containing an `observable` which yields a stream of event objects
* returned by the subscription resolver.
*
* A Source Event Stream represents a sequence of events, each of which triggers
* a GraphQL execution for that event.
*
* This may be useful when hosting the stateful subscription service in a
* different process or machine than the stateless GraphQL execution engine,
* or otherwise separating these two steps. For more on this, see the
* "Supporting Subscriptions at Scale" information in the GraphQL specification.
*/
func createSourceEventStream(
queryStrategy: QueryFieldExecutionStrategy,
mutationStrategy: MutationFieldExecutionStrategy,
subscriptionStrategy: SubscriptionFieldExecutionStrategy,
instrumentation: Instrumentation,
schema: GraphQLSchema,
documentAST: Document,
rootValue: Any,
context: Any,
eventLoopGroup: EventLoopGroup,
variableValues: [String: Map] = [:],
operationName: String? = nil
) -> EventLoopFuture<SourceEventStreamResult> {
let executeStarted = instrumentation.now
do {
// If a valid context cannot be created due to incorrect arguments,
// this will throw an error.
let exeContext = try buildExecutionContext(
queryStrategy: queryStrategy,
mutationStrategy: mutationStrategy,
subscriptionStrategy: subscriptionStrategy,
instrumentation: instrumentation,
schema: schema,
documentAST: documentAST,
rootValue: rootValue,
context: context,
eventLoopGroup: eventLoopGroup,
rawVariableValues: variableValues,
operationName: operationName
)
return try executeSubscription(context: exeContext, eventLoopGroup: eventLoopGroup)
} catch let error as GraphQLError {
instrumentation.operationExecution(
processId: processId(),
threadId: threadId(),
started: executeStarted,
finished: instrumentation.now,
schema: schema,
document: documentAST,
rootValue: rootValue,
eventLoopGroup: eventLoopGroup,
variableValues: variableValues,
operation: nil,
errors: [error],
result: nil
)
return eventLoopGroup.next().makeSucceededFuture(SourceEventStreamResult(errors: [error]))
} catch {
return eventLoopGroup.next()
.makeSucceededFuture(SourceEventStreamResult(errors: [GraphQLError(error)]))
}
}
func executeSubscription(
context: ExecutionContext,
eventLoopGroup: EventLoopGroup
) throws -> EventLoopFuture<SourceEventStreamResult> {
// Get the first node
let type = try getOperationRootType(schema: context.schema, operation: context.operation)
var inputFields: OrderedDictionary<String, [Field]> = [:]
var visitedFragmentNames: [String: Bool] = [:]
let fields = try collectFields(
exeContext: context,
runtimeType: type,
selectionSet: context.operation.selectionSet,
fields: &inputFields,
visitedFragmentNames: &visitedFragmentNames
)
// If query is valid, fields should have at least 1 member
guard
let responseName = fields.keys.first,
let fieldNodes = fields[responseName],
let fieldNode = fieldNodes.first
else {
throw GraphQLError(
message: "Subscription field resolution resulted in no field nodes."
)
}
guard let fieldDef = getFieldDef(schema: context.schema, parentType: type, fieldAST: fieldNode)
else {
throw GraphQLError(
message: "The subscription field '\(fieldNode.name.value)' is not defined.",
nodes: fieldNodes
)
}
// Implements the "ResolveFieldEventStream" algorithm from GraphQL specification.
// It differs from "ResolveFieldValue" due to providing a different `resolveFn`.
// Build a map of arguments from the field.arguments AST, using the
// variables scope to fulfill any variable references.
let args = try getArgumentValues(
argDefs: fieldDef.args,
argASTs: fieldNode.arguments,
variables: context.variableValues
)
// The resolve function's optional third argument is a context value that
// is provided to every resolve function within an execution. It is commonly
// used to represent an authenticated user, or request-specific caches.
let contextValue = context.context
// The resolve function's optional fourth argument is a collection of
// information about the current execution state.
let path = IndexPath().appending(fieldNode.name.value)
let info = GraphQLResolveInfo(
fieldName: fieldDef.name,
fieldASTs: fieldNodes,
returnType: fieldDef.type,
parentType: type,
path: path,
schema: context.schema,
fragments: context.fragments,
rootValue: context.rootValue,
operation: context.operation,
variableValues: context.variableValues
)
// Call the `subscribe()` resolver or the default resolver to produce an
// Observable yielding raw payloads.
let resolve = fieldDef.subscribe ?? defaultResolve
// Get the resolve func, regardless of if its result is normal
// or abrupt (error).
let resolvedFutureOrError = resolveOrError(
resolve: resolve,
source: context.rootValue,
args: args,
context: contextValue,
eventLoopGroup: eventLoopGroup,
info: info
)
let resolvedFuture: Future<Any?>
switch resolvedFutureOrError {
case let .failure(error):
if let graphQLError = error as? GraphQLError {
throw graphQLError
} else {
throw GraphQLError(error)
}
case let .success(success):
resolvedFuture = success
}
return resolvedFuture.map { resolved -> SourceEventStreamResult in
if !context.errors.isEmpty {
return SourceEventStreamResult(errors: context.errors)
} else if let error = resolved as? GraphQLError {
return SourceEventStreamResult(errors: [error])
} else if let stream = resolved as? EventStream<Any> {
return SourceEventStreamResult(stream: stream)
} else if resolved == nil {
return SourceEventStreamResult(errors: [
GraphQLError(message: "Resolved subscription was nil"),
])
} else {
let resolvedObj = resolved as AnyObject
return SourceEventStreamResult(errors: [
GraphQLError(
message: "Subscription field resolver must return EventStream<Any>. Received: '\(resolvedObj)'"
),
])
}
}
}
// Subscription resolvers MUST return observables that are declared as 'Any' due to Swift not having
// covariant generic support for type
// checking. Normal resolvers for subscription fields should handle type casting, same as resolvers
// for query fields.
struct SourceEventStreamResult {
public let stream: EventStream<Any>?
public let errors: [GraphQLError]
public init(stream: EventStream<Any>? = nil, errors: [GraphQLError] = []) {
self.stream = stream
self.errors = errors
}
}