Skip to content

Commit c6da6b8

Browse files
author
Jay Herron
committed
Adds primary subscription entry point function
1 parent 292091a commit c6da6b8

File tree

2 files changed

+131
-9
lines changed

2 files changed

+131
-9
lines changed

Sources/GraphQL/GraphQL.swift

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import Foundation
22
import NIO
3+
import RxSwift
34

45
public struct GraphQLResult : Equatable, Codable, CustomStringConvertible {
56
public var data: Map?
@@ -151,3 +152,66 @@ public func graphql<Retrieval: PersistedQueryRetrieval>(
151152
)
152153
}
153154
}
155+
156+
/// This is the primary entry point function for fulfilling GraphQL subscription
157+
/// operations by parsing, validating, and executing a GraphQL subscription
158+
/// document along side a GraphQL schema.
159+
///
160+
/// More sophisticated GraphQL servers, such as those which persist queries,
161+
/// may wish to separate the validation and execution phases to a static time
162+
/// tooling step, and a server runtime step.
163+
///
164+
/// - parameter queryStrategy: The field execution strategy to use for query requests
165+
/// - parameter mutationStrategy: The field execution strategy to use for mutation requests
166+
/// - parameter subscriptionStrategy: The field execution strategy to use for subscription requests
167+
/// - parameter instrumentation: The instrumentation implementation to call during the parsing, validating, execution, and field resolution stages.
168+
/// - parameter schema: The GraphQL type system to use when validating and executing a query.
169+
/// - parameter request: A GraphQL language formatted string representing the requested operation.
170+
/// - parameter rootValue: The value provided as the first argument to resolver functions on the top level type (e.g. the query object type).
171+
/// - parameter contextValue: A context value provided to all resolver functions
172+
/// - parameter variableValues: A mapping of variable name to runtime value to use for all variables defined in the `request`.
173+
/// - parameter operationName: The name of the operation to use if `request` contains multiple possible operations. Can be omitted if `request` contains only one operation.
174+
///
175+
/// - throws: throws GraphQLError if an error occurs while parsing the `request`.
176+
///
177+
/// - returns: returns a SubscriptionResult containing the subscription observable inside the key `observable` and any validation or execution errors inside the key `errors`. The
178+
/// value of `observable` might be `null` if, for example, the query is invalid. It's not possible to have both `observable` and `errors`. The observable payloads are
179+
/// GraphQLResults which contain 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`.
180+
/// 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
181+
/// will be an error inside `errors` specifying the reason for the failure and the path of the failed field.
182+
public func graphqlSubscribe(
183+
queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(),
184+
mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(),
185+
subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(),
186+
instrumentation: Instrumentation = NoOpInstrumentation,
187+
schema: GraphQLSchema,
188+
request: String,
189+
rootValue: Any = Void(),
190+
context: Any = Void(),
191+
eventLoopGroup: EventLoopGroup,
192+
variableValues: [String: Map] = [:],
193+
operationName: String? = nil
194+
) throws -> Future<SubscriptionResult> {
195+
196+
let source = Source(body: request, name: "GraphQL Subscription request")
197+
let documentAST = try parse(instrumentation: instrumentation, source: source)
198+
let validationErrors = validate(instrumentation: instrumentation, schema: schema, ast: documentAST)
199+
200+
guard validationErrors.isEmpty else {
201+
return eventLoopGroup.next().makeSucceededFuture(SubscriptionResult(errors: validationErrors))
202+
}
203+
204+
return subscribe(
205+
queryStrategy: queryStrategy,
206+
mutationStrategy: mutationStrategy,
207+
subscriptionStrategy: subscriptionStrategy,
208+
instrumentation: instrumentation,
209+
schema: schema,
210+
documentAST: documentAST,
211+
rootValue: rootValue,
212+
context: context,
213+
eventLoopGroup: eventLoopGroup,
214+
variableValues: variableValues,
215+
operationName: operationName
216+
)
217+
}

Tests/GraphQLTests/Subscription/SubscriptionTests.swift

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,62 @@ import RxSwift
55

66
/// This follows the graphql-js testing, with deviations where noted.
77
class SubscriptionTests : XCTestCase {
8-
8+
9+
// MARK: Test primary graphqlSubscribe function
10+
11+
/// This is not present in graphql-js.
12+
func testGraphqlSubscribe() throws {
13+
let db = EmailDb()
14+
let schema = db.defaultSchema()
15+
let query = """
16+
subscription ($priority: Int = 0) {
17+
importantEmail(priority: $priority) {
18+
email {
19+
from
20+
subject
21+
}
22+
inbox {
23+
unread
24+
total
25+
}
26+
}
27+
}
28+
"""
29+
30+
let subscriptionResult = try graphqlSubscribe(
31+
schema: schema,
32+
request: query,
33+
eventLoopGroup: eventLoopGroup
34+
).wait()
35+
36+
let observable = subscriptionResult.observable!
37+
38+
var currentResult = GraphQLResult()
39+
let _ = observable.subscribe { event in
40+
currentResult = try! event.element!.wait()
41+
}.disposed(by: db.disposeBag)
42+
43+
db.trigger(email: Email(
44+
45+
subject: "Alright",
46+
message: "Tests are good",
47+
unread: true
48+
))
49+
50+
XCTAssertEqual(currentResult, GraphQLResult(
51+
data: ["importantEmail": [
52+
"inbox":[
53+
"total": 2,
54+
"unread": 1
55+
],
56+
"email":[
57+
"subject": "Alright",
58+
59+
]
60+
]]
61+
))
62+
}
63+
964
// MARK: Subscription Initialization Phase
1065

1166
/// accepts multiple subscription fields defined in schema
@@ -701,12 +756,9 @@ class EmailDb {
701756
publisher.onNext(email)
702757
}
703758

704-
/// Generates a subscription to the database using a default schema and resolvers
705-
func subscription (
706-
query:String,
707-
variableValues: [String: Map] = [:]
708-
) throws -> SubscriptionObservable {
709-
let schema = emailSchemaWithResolvers(
759+
/// Returns the default email schema, with standard resolvers.
760+
func defaultSchema() -> GraphQLSchema {
761+
return emailSchemaWithResolvers(
710762
resolve: {emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture<Any?> in
711763
let email = emailAny as! Email
712764
return eventLoopGroup.next().makeSucceededFuture(EmailEvent(
@@ -718,8 +770,14 @@ class EmailDb {
718770
return eventLoopGroup.next().makeSucceededFuture(self.publisher)
719771
}
720772
)
721-
722-
return try createSubscription(schema: schema, query: query, variableValues: variableValues)
773+
}
774+
775+
/// Generates a subscription to the database using the default schema and resolvers
776+
func subscription (
777+
query:String,
778+
variableValues: [String: Map] = [:]
779+
) throws -> SubscriptionObservable {
780+
return try createSubscription(schema: defaultSchema(), query: query, variableValues: variableValues)
723781
}
724782
}
725783

0 commit comments

Comments
 (0)