diff --git a/MIGRATION.md b/MIGRATION.md index b5c3d3ce..325fc0cd 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,6 +1,30 @@ # Migration -## 2.0 to 3.0 +## 3 to 4 + +### NIO removal + +All NIO-based arguments and return types were removed, including all `EventLoopGroup` and `EventLoopFuture` parameters. + +As such, all `execute` and `subscribe` calls should have the `eventLoopGroup` argument removed, and the `await` keyword should be used. + +Also, all resolver closures must remove the `eventLoopGroup` argument, and all that return an `EventLoopFuture` should be converted to an `async` function. + +The documentation here will be very helpful in the conversion: https://www.swift.org/documentation/server/guides/libraries/concurrency-adoption-guidelines.html + +### `ConcurrentDispatchFieldExecutionStrategy` + +This was changed to `ConcurrentFieldExecutionStrategy`, and takes no parameters. + +### EventStream removal + +The `EventStream` abstraction used to provide pre-concurrency subscription support has been removed. This means that `graphqlSubscribe(...).stream` will now be an `AsyncThrowingStream` type, instead of an `EventStream` type, and that downcasting to `ConcurrentEventStream` is no longer necessary. + +### Instrumentation removal + +The `Instrumentation` type has been removed, with anticipated support for tracing using [`swift-distributed-tracing`](https://github.com/apple/swift-distributed-tracing). `instrumentation` arguments must be removed from `graphql` and `graphqlSubscribe` calls. + +## 2 to 3 ### TypeReference removal @@ -73,4 +97,4 @@ The following type properties were changed from arrays to closures. To get the a ### GraphQL type codability -With GraphQL type definitions now including closures, many of the objects in [Definition](https://github.com/GraphQLSwift/GraphQL/blob/main/Sources/GraphQL/Type/Definition.swift) are no longer codable. If you are depending on codability, you can conform the type appropriately in your downstream package. \ No newline at end of file +With GraphQL type definitions now including closures, many of the objects in [Definition](https://github.com/GraphQLSwift/GraphQL/blob/main/Sources/GraphQL/Type/Definition.swift) are no longer codable. If you are depending on codability, you can conform the type appropriately in your downstream package. diff --git a/Package.resolved b/Package.resolved index d4fd520f..a9ec17d4 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,14 +1,5 @@ { "pins" : [ - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" - } - }, { "identity" : "swift-collections", "kind" : "remoteSourceControl", @@ -17,24 +8,6 @@ "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", "version" : "1.1.4" } - }, - { - "identity" : "swift-nio", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-nio.git", - "state" : { - "revision" : "27c839f4700069928196cd0e9fa03b22f297078a", - "version" : "2.78.0" - } - }, - { - "identity" : "swift-system", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-system.git", - "state" : { - "revision" : "c8a44d836fe7913603e246acab7c528c2e780168", - "version" : "1.4.0" - } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index 2838065f..9bf74c2f 100644 --- a/Package.swift +++ b/Package.swift @@ -3,18 +3,17 @@ import PackageDescription let package = Package( name: "GraphQL", + platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)], products: [ .library(name: "GraphQL", targets: ["GraphQL"]), ], dependencies: [ - .package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.10.1")), .package(url: "https://github.com/apple/swift-collections", .upToNextMajor(from: "1.0.0")), ], targets: [ .target( name: "GraphQL", dependencies: [ - .product(name: "NIO", package: "swift-nio"), .product(name: "OrderedCollections", package: "swift-collections"), ] ), diff --git a/README.md b/README.md index 87aeef25..8f94cb5b 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,7 @@ Once a schema has been defined queries may be executed against it using the glob ```swift let result = try await graphql( schema: schema, - request: "{ hello }", - eventLoopGroup: eventLoopGroup + request: "{ hello }" ) ``` @@ -59,7 +58,7 @@ The result of this query is a `GraphQLResult` that encodes to the following JSON ### Subscription This package supports GraphQL subscription, but until the integration of `AsyncSequence` in Swift 5.5 the standard Swift library did not -provide an event-stream construct. For historical reasons and backwards compatibility, this library implements subscriptions using an +provide an event-stream construct. For historical reasons and backwards compatibility, this library implements subscriptions using an `EventStream` protocol that nearly every asynchronous stream implementation can conform to. To create a subscription field in a GraphQL schema, use the `subscribe` resolver that returns an `EventStream`. You must also provide a @@ -70,13 +69,13 @@ let schema = try GraphQLSchema( subscribe: GraphQLObjectType( name: "Subscribe", fields: [ - "hello": GraphQLField( + "hello": GraphQLField( type: GraphQLString, resolve: { eventResult, _, _, _, _ in // Defines how to transform each event when it occurs return eventResult }, subscribe: { _, _, _, _, _ in // Defines how to construct the event stream - let asyncStream = AsyncThrowingStream { continuation in + return AsyncThrowingStream { continuation in let timer = Timer.scheduledTimer( withTimeInterval: 3, repeats: true, @@ -84,7 +83,6 @@ let schema = try GraphQLSchema( continuation.yield("world") // Emits "world" every 3 seconds } } - return ConcurrentEventStream(asyncStream) } ) ] @@ -98,8 +96,6 @@ To execute a subscription use the `graphqlSubscribe` function: let subscriptionResult = try await graphqlSubscribe( schema: schema, ) -// Must downcast from EventStream to concrete type to use in 'for await' loop below -let concurrentStream = subscriptionResult.stream! as! ConcurrentEventStream for try await result in concurrentStream.stream { print(result) } @@ -111,18 +107,15 @@ The code above will print the following JSON every 3 seconds: { "hello": "world" } ``` -The example above assumes that your environment has access to Swift Concurrency. If that is not the case, try using -[GraphQLRxSwift](https://github.com/GraphQLSwift/GraphQLRxSwift) - ## Encoding Results -If you encode a `GraphQLResult` with an ordinary `JSONEncoder`, there are no guarantees that the field order will match the query, +If you encode a `GraphQLResult` with an ordinary `JSONEncoder`, there are no guarantees that the field order will match the query, violating the [GraphQL spec](https://spec.graphql.org/June2018/#sec-Serialized-Map-Ordering). To preserve this order, `GraphQLResult` should be encoded using the `GraphQLJSONEncoder` provided by this package. ## Support -This package supports Swift versions in [alignment with Swift NIO](https://github.com/apple/swift-nio?tab=readme-ov-file#swift-versions). +This package aims to support the previous three Swift versions. For details on upgrading to new major versions, see [MIGRATION](MIGRATION.md). @@ -140,7 +133,7 @@ To format your code, install `swiftformat` and run: ```bash swiftformat . -``` +``` Most of this repo mirrors the structure of (the canonical GraphQL implementation written in Javascript/Typescript)[https://github.com/graphql/graphql-js]. If there is any feature diff --git a/Sources/GraphQL/Execution/Execute.swift b/Sources/GraphQL/Execution/Execute.swift index f082b721..403b65ab 100644 --- a/Sources/GraphQL/Execution/Execute.swift +++ b/Sources/GraphQL/Execution/Execute.swift @@ -1,5 +1,4 @@ import Dispatch -import NIO import OrderedCollections /** @@ -32,12 +31,10 @@ public final class ExecutionContext { let queryStrategy: QueryFieldExecutionStrategy let mutationStrategy: MutationFieldExecutionStrategy let subscriptionStrategy: SubscriptionFieldExecutionStrategy - let instrumentation: Instrumentation public let schema: GraphQLSchema public let fragments: [String: FragmentDefinition] public let rootValue: Any public let context: Any - public let eventLoopGroup: EventLoopGroup public let operation: OperationDefinition public let variableValues: [String: Map] @@ -56,12 +53,10 @@ public final class ExecutionContext { queryStrategy: QueryFieldExecutionStrategy, mutationStrategy: MutationFieldExecutionStrategy, subscriptionStrategy: SubscriptionFieldExecutionStrategy, - instrumentation: Instrumentation, schema: GraphQLSchema, fragments: [String: FragmentDefinition], rootValue: Any, context: Any, - eventLoopGroup: EventLoopGroup, operation: OperationDefinition, variableValues: [String: Map], errors: [GraphQLError] @@ -69,12 +64,10 @@ public final class ExecutionContext { self.queryStrategy = queryStrategy self.mutationStrategy = mutationStrategy self.subscriptionStrategy = subscriptionStrategy - self.instrumentation = instrumentation self.schema = schema self.fragments = fragments self.rootValue = rootValue self.context = context - self.eventLoopGroup = eventLoopGroup self.operation = operation self.variableValues = variableValues _errors = errors @@ -96,7 +89,7 @@ public protocol FieldExecutionStrategy { sourceValue: Any, path: IndexPath, fields: OrderedDictionary - ) throws -> Future> + ) async throws -> OrderedDictionary } public protocol MutationFieldExecutionStrategy: FieldExecutionStrategy {} @@ -117,30 +110,20 @@ public struct SerialFieldExecutionStrategy: QueryFieldExecutionStrategy, sourceValue: Any, path: IndexPath, fields: OrderedDictionary - ) throws -> Future> { + ) async throws -> OrderedDictionary { var results = OrderedDictionary() - - return fields - .reduce(exeContext.eventLoopGroup.next().makeSucceededVoidFuture()) { prev, field in - // We use ``flatSubmit`` here to avoid a stack overflow issue with EventLoopFutures. - // See: https://github.com/apple/swift-nio/issues/970 - exeContext.eventLoopGroup.next().flatSubmit { - prev.tryFlatMap { - let fieldASTs = field.value - let fieldPath = path.appending(field.key) - - return try resolveField( - exeContext: exeContext, - parentType: parentType, - source: sourceValue, - fieldASTs: fieldASTs, - path: fieldPath - ).map { result in - results[field.key] = result ?? Map.null - } - } - } - }.map { results } + for field in fields { + let fieldASTs = field.value + let fieldPath = path.appending(field.key) + results[field.key] = try await resolveField( + exeContext: exeContext, + parentType: parentType, + source: sourceValue, + fieldASTs: fieldASTs, + path: fieldPath + ) ?? Map.null + } + return results } } @@ -149,78 +132,38 @@ public struct SerialFieldExecutionStrategy: QueryFieldExecutionStrategy, * * Each field is resolved as an individual task on a concurrent dispatch queue. */ -public struct ConcurrentDispatchFieldExecutionStrategy: QueryFieldExecutionStrategy, +public struct ConcurrentFieldExecutionStrategy: QueryFieldExecutionStrategy, SubscriptionFieldExecutionStrategy { - let dispatchQueue: DispatchQueue - - public init(dispatchQueue: DispatchQueue) { - self.dispatchQueue = dispatchQueue - } - - public init( - queueLabel: String = "GraphQL field execution", - queueQoS: DispatchQoS = .userInitiated - ) { - dispatchQueue = DispatchQueue( - label: queueLabel, - qos: queueQoS, - attributes: .concurrent - ) - } - public func executeFields( exeContext: ExecutionContext, parentType: GraphQLObjectType, sourceValue: Any, path: IndexPath, fields: OrderedDictionary - ) throws -> Future> { - let resultsQueue = DispatchQueue( - label: "\(dispatchQueue.label) results", - qos: dispatchQueue.qos - ) - - let group = DispatchGroup() - // preserve field order by assigning to null and filtering later - var results: OrderedDictionary?> = fields - .mapValues { _ -> Future? in nil } - var err: Error? - - for field in fields { - let fieldASTs = field.value - let fieldKey = field.key - let fieldPath = path.appending(fieldKey) - dispatchQueue.async(group: group) { - guard err == nil else { - return - } - do { - let result = try resolveField( + ) async throws -> OrderedDictionary { + return try await withThrowingTaskGroup(of: (String, Any?).self) { group in + // preserve field order by assigning to null and filtering later + var results: OrderedDictionary = fields.mapValues { _ -> Any? in nil } + for field in fields { + group.addTask { + let fieldASTs = field.value + let fieldPath = path.appending(field.key) + let result = try await resolveField( exeContext: exeContext, parentType: parentType, source: sourceValue, fieldASTs: fieldASTs, path: fieldPath - ) - resultsQueue.async(group: group) { - results[fieldKey] = result.map { $0 ?? Map.null } - } - } catch { - resultsQueue.async(group: group) { - err = error - } + ) ?? Map.null + return (field.key, result) } } + for try await result in group { + results[result.0] = result.1 + } + return results.compactMapValues { $0 } } - - group.wait() - - if let error = err { - throw error - } - - return results.compactMapValues { $0 }.flatten(on: exeContext.eventLoopGroup) } } @@ -234,16 +177,13 @@ func execute( queryStrategy: QueryFieldExecutionStrategy, mutationStrategy: MutationFieldExecutionStrategy, subscriptionStrategy: SubscriptionFieldExecutionStrategy, - instrumentation: Instrumentation, schema: GraphQLSchema, documentAST: Document, rootValue: Any, context: Any, - eventLoopGroup: EventLoopGroup, variableValues: [String: Map] = [:], operationName: String? = nil -) -> Future { - let executeStarted = instrumentation.now +) async throws -> GraphQLResult { let buildContext: ExecutionContext do { @@ -253,90 +193,44 @@ func execute( queryStrategy: queryStrategy, mutationStrategy: mutationStrategy, subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, schema: schema, documentAST: documentAST, rootValue: rootValue, context: context, - eventLoopGroup: eventLoopGroup, rawVariableValues: variableValues, operationName: operationName ) } 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(GraphQLResult(errors: [error])) + return GraphQLResult(errors: [error]) } catch { - return eventLoopGroup.next() - .makeSucceededFuture(GraphQLResult(errors: [GraphQLError(error)])) + return GraphQLResult(errors: [GraphQLError(error)]) } do { // var executeErrors: [GraphQLError] = [] - - return try executeOperation( + let data = try await executeOperation( exeContext: buildContext, operation: buildContext.operation, rootValue: rootValue - ).flatMapThrowing { data -> GraphQLResult in - var dataMap: Map = [:] + ) + var dataMap: Map = [:] - for (key, value) in data { - dataMap[key] = try map(from: value) - } + for (key, value) in data { + dataMap[key] = try map(from: value) + } - var result: GraphQLResult = .init(data: dataMap) + var result: GraphQLResult = .init(data: dataMap) - if !buildContext.errors.isEmpty { - result.errors = buildContext.errors - } + if !buildContext.errors.isEmpty { + result.errors = buildContext.errors + } // executeErrors = buildContext.errors - return result - }.flatMapError { error -> Future in - let result: GraphQLResult - if let error = error as? GraphQLError { - result = GraphQLResult(errors: [error]) - } else { - result = GraphQLResult(errors: [GraphQLError(error)]) - } - - return buildContext.eventLoopGroup.next().makeSucceededFuture(result) - }.map { result -> GraphQLResult in -// instrumentation.operationExecution( -// processId: processId(), -// threadId: threadId(), -// started: executeStarted, -// finished: instrumentation.now, -// schema: schema, -// document: documentAST, -// rootValue: rootValue, -// eventLoopGroup: eventLoopGroup, -// variableValues: variableValues, -// operation: buildContext.operation, -// errors: executeErrors, -// result: result -// ) - result - } + return result } catch let error as GraphQLError { - return eventLoopGroup.next().makeSucceededFuture(GraphQLResult(errors: [error])) + return GraphQLResult(errors: [error]) } catch { - return eventLoopGroup.next() - .makeSucceededFuture(GraphQLResult(errors: [GraphQLError(error)])) + return GraphQLResult(errors: [GraphQLError(error)]) } } @@ -350,12 +244,10 @@ func buildExecutionContext( queryStrategy: QueryFieldExecutionStrategy, mutationStrategy: MutationFieldExecutionStrategy, subscriptionStrategy: SubscriptionFieldExecutionStrategy, - instrumentation: Instrumentation, schema: GraphQLSchema, documentAST: Document, rootValue: Any, context: Any, - eventLoopGroup: EventLoopGroup, rawVariableValues: [String: Map], operationName: String? ) throws -> ExecutionContext { @@ -405,12 +297,10 @@ func buildExecutionContext( queryStrategy: queryStrategy, mutationStrategy: mutationStrategy, subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, schema: schema, fragments: fragments, rootValue: rootValue, context: context, - eventLoopGroup: eventLoopGroup, operation: operation, variableValues: variableValues, errors: errors @@ -424,7 +314,7 @@ func executeOperation( exeContext: ExecutionContext, operation: OperationDefinition, rootValue: Any -) throws -> Future> { +) async throws -> OrderedDictionary { let type = try getOperationRootType(schema: exeContext.schema, operation: operation) var inputFields: OrderedDictionary = [:] var visitedFragmentNames: [String: Bool] = [:] @@ -448,7 +338,7 @@ func executeOperation( fieldExecutionStrategy = exeContext.subscriptionStrategy } - return try fieldExecutionStrategy.executeFields( + return try await fieldExecutionStrategy.executeFields( exeContext: exeContext, parentType: type, sourceValue: rootValue, @@ -687,7 +577,7 @@ public func resolveField( source: Any, fieldASTs: [Field], path: IndexPath -) throws -> Future { +) async throws -> Any? { let fieldAST = fieldASTs[0] let fieldName = fieldAST.name.value @@ -729,32 +619,17 @@ public func resolveField( variableValues: exeContext.variableValues ) -// let resolveFieldStarted = exeContext.instrumentation.now - // Get the resolve func, regardless of if its result is normal // or abrupt (error). - let result = resolveOrError( + let result = await resolveOrError( resolve: resolve, source: source, args: args, context: context, - eventLoopGroup: exeContext.eventLoopGroup, info: info ) -// exeContext.instrumentation.fieldResolution( -// processId: processId(), -// threadId: threadId(), -// started: resolveFieldStarted, -// finished: exeContext.instrumentation.now, -// source: source, -// args: args, -// eventLoopGroup: exeContext.eventLoopGroup, -// info: info, -// result: result -// ) - - return try completeValueCatchingError( + return try await completeValueCatchingError( exeContext: exeContext, returnType: returnType, fieldASTs: fieldASTs, @@ -771,11 +646,10 @@ func resolveOrError( source: Any, args: Map, context: Any, - eventLoopGroup: EventLoopGroup, info: GraphQLResolveInfo -) -> Result, Error> { +) async -> Result { do { - let result = try resolve(source, args, context, eventLoopGroup, info) + let result = try await resolve(source, args, context, info) return .success(result) } catch { return .failure(error) @@ -790,12 +664,12 @@ func completeValueCatchingError( fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, - result: Result, Error> -) throws -> Future { + result: Result +) async throws -> Any? { // If the field type is non-nullable, then it is resolved without any // protection from errors, however it still properly locates the error. if let returnType = returnType as? GraphQLNonNull { - return try completeValueWithLocatedError( + return try await completeValueWithLocatedError( exeContext: exeContext, returnType: returnType, fieldASTs: fieldASTs, @@ -808,29 +682,21 @@ func completeValueCatchingError( // Otherwise, error protection is applied, logging the error and resolving // a null value for this field if one is encountered. do { - let completed = try completeValueWithLocatedError( + return try await completeValueWithLocatedError( exeContext: exeContext, returnType: returnType, fieldASTs: fieldASTs, info: info, path: path, result: result - ).flatMapError { error -> EventLoopFuture in - guard let error = error as? GraphQLError else { - return exeContext.eventLoopGroup.next().makeFailedFuture(error) - } - exeContext.append(error: error) - return exeContext.eventLoopGroup.next().makeSucceededFuture(nil) - } - - return completed + ) } catch let error as GraphQLError { // If `completeValueWithLocatedError` returned abruptly (threw an error), // log the error and return .null. exeContext.append(error: error) - return exeContext.eventLoopGroup.next().makeSucceededFuture(nil) + return nil } catch { - return exeContext.eventLoopGroup.next().makeFailedFuture(error) + throw error } } @@ -842,20 +708,17 @@ func completeValueWithLocatedError( fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, - result: Result, Error> -) throws -> Future { + result: Result +) async throws -> Any? { do { - let completed = try completeValue( + return try await completeValue( exeContext: exeContext, returnType: returnType, fieldASTs: fieldASTs, info: info, path: path, result: result - ).flatMapErrorThrowing { error -> Any? in - throw locatedError(originalError: error, nodes: fieldASTs, path: path) - } - return completed + ) } catch { throw locatedError( originalError: error, @@ -892,8 +755,8 @@ func completeValue( fieldASTs: [Field], info: GraphQLResolveInfo, path: IndexPath, - result: Result, Error> -) throws -> Future { + result: Result +) async throws -> Any? { switch result { case let .failure(error): throw error @@ -901,79 +764,75 @@ func completeValue( // If field type is NonNull, complete for inner type, and throw field error // if result is nullish. if let returnType = returnType as? GraphQLNonNull { - return try completeValue( + let value = try await completeValue( exeContext: exeContext, returnType: returnType.ofType, fieldASTs: fieldASTs, info: info, path: path, result: .success(result) - ).flatMapThrowing { value -> Any? in - guard let value = value else { - throw GraphQLError( - message: "Cannot return null for non-nullable field \(info.parentType.name).\(info.fieldName)." - ) - } - - return value + ) + guard let value = value else { + throw GraphQLError( + message: "Cannot return null for non-nullable field \(info.parentType.name).\(info.fieldName)." + ) } - } - return result.tryFlatMap { result throws -> Future in - // If result value is null-ish (nil or .null) then return .null. - guard let result = result, let r = unwrap(result) else { - return exeContext.eventLoopGroup.next().makeSucceededFuture(nil) - } + return value + } - // If field type is List, complete each item in the list with the inner type - if let returnType = returnType as? GraphQLList { - return try completeListValue( - exeContext: exeContext, - returnType: returnType, - fieldASTs: fieldASTs, - info: info, - path: path, - result: r - ).map { $0 } - } + // If result value is null-ish (nil or .null) then return .null. + guard let result = result, let r = unwrap(result) else { + return nil + } - // If field type is a leaf type, Scalar or Enum, serialize to a valid value, - // returning .null if serialization is not possible. - if let returnType = returnType as? GraphQLLeafType { - return try exeContext.eventLoopGroup.next() - .makeSucceededFuture(completeLeafValue(returnType: returnType, result: r)) - } + // If field type is List, complete each item in the list with the inner type + if let returnType = returnType as? GraphQLList { + return try await completeListValue( + exeContext: exeContext, + returnType: returnType, + fieldASTs: fieldASTs, + info: info, + path: path, + result: r + ) + } - // If field type is an abstract type, Interface or Union, determine the - // runtime Object type and complete for that type. - if let returnType = returnType as? GraphQLAbstractType { - return try completeAbstractValue( - exeContext: exeContext, - returnType: returnType, - fieldASTs: fieldASTs, - info: info, - path: path, - result: r - ) - } + // If field type is a leaf type, Scalar or Enum, serialize to a valid value, + // returning .null if serialization is not possible. + if let returnType = returnType as? GraphQLLeafType { + return try completeLeafValue(returnType: returnType, result: r) + } - // If field type is Object, execute and complete all sub-selections. - if let returnType = returnType as? GraphQLObjectType { - return try completeObjectValue( - exeContext: exeContext, - returnType: returnType, - fieldASTs: fieldASTs, - info: info, - path: path, - result: r - ) - } + // If field type is an abstract type, Interface or Union, determine the + // runtime Object type and complete for that type. + if let returnType = returnType as? GraphQLAbstractType { + return try await completeAbstractValue( + exeContext: exeContext, + returnType: returnType, + fieldASTs: fieldASTs, + info: info, + path: path, + result: r + ) + } - // Not reachable. All possible output types have been considered. - throw GraphQLError( - message: "Cannot complete value of unexpected type \"\(returnType)\"." + // If field type is Object, execute and complete all sub-selections. + if let returnType = returnType as? GraphQLObjectType { + return try await completeObjectValue( + exeContext: exeContext, + returnType: returnType, + fieldASTs: fieldASTs, + info: info, + path: path, + result: r ) } + + // Not reachable. All possible output types have been considered. + throw GraphQLError( + message: "Cannot complete value of unexpected type \"\(returnType)\"." + ) } } @@ -988,7 +847,7 @@ func completeListValue( info: GraphQLResolveInfo, path: IndexPath, result: Any -) throws -> Future<[Any?]> { +) async throws -> [Any?] { guard let result = result as? [Any?] else { throw GraphQLError( message: @@ -998,28 +857,32 @@ func completeListValue( } let itemType = returnType.ofType - var completedResults: [Future] = [] - for (index, item) in result.enumerated() { - // No need to modify the info object containing the path, - // since from here on it is not ever accessed by resolver funcs. - let fieldPath = path.appending(index) - let futureItem = item as? Future ?? exeContext.eventLoopGroup.next() - .makeSucceededFuture(item) + return try await withThrowingTaskGroup(of: (Int, Any?).self) { group in + // To preserve order, match size to result, and filter out nils at the end. + var results: [Any?] = result.map { _ in nil } + for (index, item) in result.enumerated() { + group.addTask { + // No need to modify the info object containing the path, + // since from here on it is not ever accessed by resolver funcs. + let fieldPath = path.appending(index) - let completedItem = try completeValueCatchingError( - exeContext: exeContext, - returnType: itemType, - fieldASTs: fieldASTs, - info: info, - path: fieldPath, - result: .success(futureItem) - ) - - completedResults.append(completedItem) + let result = try await completeValueCatchingError( + exeContext: exeContext, + returnType: itemType, + fieldASTs: fieldASTs, + info: info, + path: fieldPath, + result: .success(item) + ) + return (index, result) + } + for try await result in group { + results[result.0] = result.1 + } + } + return results.compactMap { $0 } } - - return completedResults.flatten(on: exeContext.eventLoopGroup) } /** @@ -1048,13 +911,12 @@ func completeAbstractValue( info: GraphQLResolveInfo, path: IndexPath, result: Any -) throws -> Future { - var resolveRes = try returnType.resolveType?(result, exeContext.eventLoopGroup, info) +) async throws -> Any? { + var resolveRes = try returnType.resolveType?(result, info) .typeResolveResult resolveRes = try resolveRes ?? defaultResolveType( value: result, - eventLoopGroup: exeContext.eventLoopGroup, info: info, abstractType: returnType ) @@ -1095,7 +957,7 @@ func completeAbstractValue( ) } - return try completeObjectValue( + return try await completeObjectValue( exeContext: exeContext, returnType: objectType, fieldASTs: fieldASTs, @@ -1115,13 +977,13 @@ func completeObjectValue( info: GraphQLResolveInfo, path: IndexPath, result: Any -) throws -> Future { +) async throws -> Any? { // If there is an isTypeOf predicate func, call it with the // current result. If isTypeOf returns false, then raise an error rather // than continuing execution. if let isTypeOf = returnType.isTypeOf, - try !isTypeOf(result, exeContext.eventLoopGroup, info) + try !isTypeOf(result, info) { throw GraphQLError( message: @@ -1146,13 +1008,13 @@ func completeObjectValue( } } - return try exeContext.queryStrategy.executeFields( + return try await exeContext.queryStrategy.executeFields( exeContext: exeContext, parentType: returnType, sourceValue: result, path: path, fields: subFieldASTs - ).map { $0 } + ) } /** @@ -1162,7 +1024,6 @@ func completeObjectValue( */ func defaultResolveType( value: Any, - eventLoopGroup: EventLoopGroup, info: GraphQLResolveInfo, abstractType: GraphQLAbstractType ) throws -> TypeResolveResult? { @@ -1170,7 +1031,7 @@ func defaultResolveType( guard let type = try possibleTypes - .find({ try $0.isTypeOf?(value, eventLoopGroup, info) ?? false }) + .find({ try $0.isTypeOf?(value, info) ?? false }) else { return nil } @@ -1187,31 +1048,30 @@ func defaultResolve( source: Any, args _: Map, context _: Any, - eventLoopGroup: EventLoopGroup, info: GraphQLResolveInfo -) -> Future { +) async throws -> Any? { guard let source = unwrap(source) else { - return eventLoopGroup.next().makeSucceededFuture(nil) + return nil } if let subscriptable = source as? KeySubscriptable { let value = subscriptable[info.fieldName] - return eventLoopGroup.next().makeSucceededFuture(value) + return value } if let subscriptable = source as? [String: Any] { let value = subscriptable[info.fieldName] - return eventLoopGroup.next().makeSucceededFuture(value) + return value } if let subscriptable = source as? OrderedDictionary { let value = subscriptable[info.fieldName] - return eventLoopGroup.next().makeSucceededFuture(value) + return value } let mirror = Mirror(reflecting: source) guard let value = mirror.getValue(named: info.fieldName) else { - return eventLoopGroup.next().makeSucceededFuture(nil) + return nil } - return eventLoopGroup.next().makeSucceededFuture(value) + return value } /** diff --git a/Sources/GraphQL/GraphQL.swift b/Sources/GraphQL/GraphQL.swift index 0cc6c643..36bc3456 100644 --- a/Sources/GraphQL/GraphQL.swift +++ b/Sources/GraphQL/GraphQL.swift @@ -1,4 +1,3 @@ -import NIO public struct GraphQLResult: Equatable, Codable, Sendable, CustomStringConvertible { public var data: Map? @@ -45,19 +44,18 @@ public struct GraphQLResult: Equatable, Codable, Sendable, CustomStringConvertib /// SubscriptionResult wraps the observable and error data returned by the subscribe request. public struct SubscriptionResult { - public let stream: SubscriptionEventStream? + public let stream: AsyncThrowingStream? public let errors: [GraphQLError] - public init(stream: SubscriptionEventStream? = nil, errors: [GraphQLError] = []) { + public init( + stream: AsyncThrowingStream? = nil, + errors: [GraphQLError] = [] + ) { self.stream = stream self.errors = errors } } -/// SubscriptionObservable represents an event stream of fully resolved GraphQL subscription -/// results. Subscribers can be added to this stream. -public typealias SubscriptionEventStream = EventStream> - /// This is the primary entry point function for fulfilling GraphQL operations /// by parsing, validating, and executing a GraphQL document along side a /// GraphQL schema. @@ -69,8 +67,6 @@ public typealias SubscriptionEventStream = EventStream> /// - parameter queryStrategy: The field execution strategy to use for query requests /// - parameter mutationStrategy: The field execution strategy to use for mutation requests /// - parameter subscriptionStrategy: The field execution strategy to use for subscription requests -/// - parameter instrumentation: The instrumentation implementation to call during the parsing, -/// validating, execution, and field resolution stages. /// - parameter schema: The GraphQL type system to use when validating and executing a /// query. /// - parameter request: A GraphQL language formatted string representing the requested @@ -95,39 +91,34 @@ public func graphql( queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(), mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(), subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(), - instrumentation: Instrumentation = NoOpInstrumentation, validationRules: [(ValidationContext) -> Visitor] = [], schema: GraphQLSchema, request: String, rootValue: Any = (), context: Any = (), - eventLoopGroup: EventLoopGroup, variableValues: [String: Map] = [:], operationName: String? = nil -) throws -> Future { +) async throws -> GraphQLResult { let source = Source(body: request, name: "GraphQL request") - let documentAST = try parse(instrumentation: instrumentation, source: source) + let documentAST = try parse(source: source) let validationErrors = validate( - instrumentation: instrumentation, schema: schema, ast: documentAST, rules: validationRules ) guard validationErrors.isEmpty else { - return eventLoopGroup.next().makeSucceededFuture(GraphQLResult(errors: validationErrors)) + return GraphQLResult(errors: validationErrors) } - return execute( + return try await execute( queryStrategy: queryStrategy, mutationStrategy: mutationStrategy, subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, schema: schema, documentAST: documentAST, rootValue: rootValue, context: context, - eventLoopGroup: eventLoopGroup, variableValues: variableValues, operationName: operationName ) @@ -139,8 +130,6 @@ public func graphql( /// - parameter queryStrategy: The field execution strategy to use for query requests /// - parameter mutationStrategy: The field execution strategy to use for mutation requests /// - parameter subscriptionStrategy: The field execution strategy to use for subscription requests -/// - parameter instrumentation: The instrumentation implementation to call during the parsing, -/// validating, execution, and field resolution stages. /// - parameter queryRetrieval: The PersistedQueryRetrieval instance to use for looking up /// queries /// - parameter queryId: The id of the query to execute @@ -164,33 +153,29 @@ public func graphql( queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(), mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(), subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(), - instrumentation: Instrumentation = NoOpInstrumentation, queryRetrieval: Retrieval, queryId: Retrieval.Id, rootValue: Any = (), context: Any = (), - eventLoopGroup: EventLoopGroup, variableValues: [String: Map] = [:], operationName: String? = nil -) throws -> Future { +) async throws -> GraphQLResult { switch try queryRetrieval.lookup(queryId) { case .unknownId: throw GraphQLError(message: "Unknown query id") case let .parseError(parseError): throw parseError case let .validateErrors(_, validationErrors): - return eventLoopGroup.next().makeSucceededFuture(GraphQLResult(errors: validationErrors)) + return GraphQLResult(errors: validationErrors) case let .result(schema, documentAST): - return execute( + return try await execute( queryStrategy: queryStrategy, mutationStrategy: mutationStrategy, subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, schema: schema, documentAST: documentAST, rootValue: rootValue, context: context, - eventLoopGroup: eventLoopGroup, variableValues: variableValues, operationName: operationName ) @@ -208,8 +193,6 @@ public func graphql( /// - parameter queryStrategy: The field execution strategy to use for query requests /// - parameter mutationStrategy: The field execution strategy to use for mutation requests /// - parameter subscriptionStrategy: The field execution strategy to use for subscription requests -/// - parameter instrumentation: The instrumentation implementation to call during the parsing, -/// validating, execution, and field resolution stages. /// - parameter schema: The GraphQL type system to use when validating and executing a /// query. /// - parameter request: A GraphQL language formatted string representing the requested @@ -238,174 +221,35 @@ public func graphqlSubscribe( queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(), mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(), subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(), - instrumentation: Instrumentation = NoOpInstrumentation, validationRules: [(ValidationContext) -> Visitor] = [], schema: GraphQLSchema, request: String, rootValue: Any = (), context: Any = (), - eventLoopGroup: EventLoopGroup, variableValues: [String: Map] = [:], operationName: String? = nil -) throws -> Future { +) async throws -> SubscriptionResult { let source = Source(body: request, name: "GraphQL Subscription request") - let documentAST = try parse(instrumentation: instrumentation, source: source) + let documentAST = try parse(source: source) let validationErrors = validate( - instrumentation: instrumentation, schema: schema, ast: documentAST, rules: validationRules ) guard validationErrors.isEmpty else { - return eventLoopGroup.next() - .makeSucceededFuture(SubscriptionResult(errors: validationErrors)) + return SubscriptionResult(errors: validationErrors) } - return subscribe( + return try await subscribe( queryStrategy: queryStrategy, mutationStrategy: mutationStrategy, subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, schema: schema, documentAST: documentAST, rootValue: rootValue, context: context, - eventLoopGroup: eventLoopGroup, variableValues: variableValues, operationName: operationName ) } - -// MARK: Async/Await - -/// This is the primary entry point function for fulfilling GraphQL operations -/// by parsing, validating, and executing a GraphQL document along side a -/// GraphQL schema. -/// -/// More sophisticated GraphQL servers, such as those which persist queries, -/// may wish to separate the validation and execution phases to a static time -/// tooling step, and a server runtime step. -/// -/// - parameter queryStrategy: The field execution strategy to use for query requests -/// - parameter mutationStrategy: The field execution strategy to use for mutation requests -/// - parameter subscriptionStrategy: The field execution strategy to use for subscription -/// requests -/// - parameter instrumentation: The instrumentation implementation to call during the -/// parsing, validating, execution, and field resolution stages. -/// - parameter schema: The GraphQL type system to use when validating and -/// executing a query. -/// - parameter request: A GraphQL language formatted string representing the -/// requested operation. -/// - parameter rootValue: The value provided as the first argument to resolver -/// functions on the top level type (e.g. the query object type). -/// - parameter contextValue: A context value provided to all resolver functions -/// functions -/// - parameter variableValues: A mapping of variable name to runtime value to use for all -/// variables defined in the `request`. -/// - parameter operationName: The name of the operation to use if `request` contains -/// multiple possible operations. Can be omitted if `request` contains only one operation. -/// -/// - throws: throws GraphQLError if an error occurs while parsing the `request`. -/// -/// - 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. -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) -public func graphql( - queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(), - mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(), - subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(), - instrumentation: Instrumentation = NoOpInstrumentation, - schema: GraphQLSchema, - request: String, - rootValue: Any = (), - context: Any = (), - eventLoopGroup: EventLoopGroup, - variableValues: [String: Map] = [:], - operationName: String? = nil -) async throws -> GraphQLResult { - return try await graphql( - queryStrategy: queryStrategy, - mutationStrategy: mutationStrategy, - subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, - schema: schema, - request: request, - rootValue: rootValue, - context: context, - eventLoopGroup: eventLoopGroup, - variableValues: variableValues, - operationName: operationName - ).get() -} - -/// This is the primary entry point function for fulfilling GraphQL subscription -/// operations by parsing, validating, and executing a GraphQL subscription -/// document along side a GraphQL schema. -/// -/// More sophisticated GraphQL servers, such as those which persist queries, -/// may wish to separate the validation and execution phases to a static time -/// tooling step, and a server runtime step. -/// -/// - parameter queryStrategy: The field execution strategy to use for query requests -/// - parameter mutationStrategy: The field execution strategy to use for mutation requests -/// - parameter subscriptionStrategy: The field execution strategy to use for subscription -/// requests -/// - parameter instrumentation: The instrumentation implementation to call during the -/// parsing, validating, execution, and field resolution stages. -/// - parameter schema: The GraphQL type system to use when validating and -/// executing a query. -/// - parameter request: A GraphQL language formatted string representing the -/// requested operation. -/// - parameter rootValue: The value provided as the first argument to resolver -/// functions on the top level type (e.g. the query object type). -/// - parameter contextValue: A context value provided to all resolver functions -/// - parameter variableValues: A mapping of variable name to runtime value to use for all -/// variables defined in the `request`. -/// - parameter operationName: The name of the operation to use if `request` contains -/// multiple possible operations. Can be omitted if `request` contains only one operation. -/// -/// - throws: throws GraphQLError if an error occurs while parsing the `request`. -/// -/// - returns: returns a SubscriptionResult containing the subscription observable inside the -/// key `observable` and any validation or execution errors inside the key `errors`. The -/// 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 -/// 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`. -/// 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. -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) -public func graphqlSubscribe( - queryStrategy: QueryFieldExecutionStrategy = SerialFieldExecutionStrategy(), - mutationStrategy: MutationFieldExecutionStrategy = SerialFieldExecutionStrategy(), - subscriptionStrategy: SubscriptionFieldExecutionStrategy = SerialFieldExecutionStrategy(), - instrumentation: Instrumentation = NoOpInstrumentation, - schema: GraphQLSchema, - request: String, - rootValue: Any = (), - context: Any = (), - eventLoopGroup: EventLoopGroup, - variableValues: [String: Map] = [:], - operationName: String? = nil -) async throws -> SubscriptionResult { - return try await graphqlSubscribe( - queryStrategy: queryStrategy, - mutationStrategy: mutationStrategy, - subscriptionStrategy: subscriptionStrategy, - instrumentation: instrumentation, - schema: schema, - request: request, - rootValue: rootValue, - context: context, - eventLoopGroup: eventLoopGroup, - variableValues: variableValues, - operationName: operationName - ).get() -} diff --git a/Sources/GraphQL/GraphQLRequest.swift b/Sources/GraphQL/GraphQLRequest.swift index bfcca8ff..035ae174 100644 --- a/Sources/GraphQL/GraphQLRequest.swift +++ b/Sources/GraphQL/GraphQLRequest.swift @@ -37,7 +37,6 @@ public struct GraphQLRequest: Equatable, Codable { /// - Returns: The operation type performed by the request public func operationType() throws -> OperationType { let documentAST = try GraphQL.parse( - instrumentation: NoOpInstrumentation, source: Source(body: query, name: "GraphQL request") ) let firstOperation = documentAST.definitions.compactMap { $0 as? OperationDefinition }.first diff --git a/Sources/GraphQL/Instrumentation/DispatchQueueInstrumentationWrapper.swift b/Sources/GraphQL/Instrumentation/DispatchQueueInstrumentationWrapper.swift deleted file mode 100644 index 73d54278..00000000 --- a/Sources/GraphQL/Instrumentation/DispatchQueueInstrumentationWrapper.swift +++ /dev/null @@ -1,141 +0,0 @@ -import Dispatch -import NIO - -/// Proxies calls through to another `Instrumentation` instance via a DispatchQueue -/// -/// Has two primary use cases: -/// 1. Allows a non thread safe Instrumentation implementation to be used along side a multithreaded -/// execution strategy -/// 2. Allows slow or heavy instrumentation processing to happen outside of the current query -/// execution -public class DispatchQueueInstrumentationWrapper: Instrumentation { - let instrumentation: Instrumentation - let dispatchQueue: DispatchQueue - let dispatchGroup: DispatchGroup? - - public init( - _ instrumentation: Instrumentation, - label: String = "GraphQL instrumentation wrapper", - qos: DispatchQoS = .utility, - attributes: DispatchQueue.Attributes = [], - dispatchGroup: DispatchGroup? = nil - ) { - self.instrumentation = instrumentation - dispatchQueue = DispatchQueue(label: label, qos: qos, attributes: attributes) - self.dispatchGroup = dispatchGroup - } - - public init( - _ instrumentation: Instrumentation, - dispatchQueue: DispatchQueue, - dispatchGroup: DispatchGroup? = nil - ) { - self.instrumentation = instrumentation - self.dispatchQueue = dispatchQueue - self.dispatchGroup = dispatchGroup - } - - public var now: DispatchTime { - return instrumentation.now - } - - public func queryParsing( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - source: Source, - result: Result - ) { - dispatchQueue.async(group: dispatchGroup) { - self.instrumentation.queryParsing( - processId: processId, - threadId: threadId, - started: started, - finished: finished, - source: source, - result: result - ) - } - } - - public func queryValidation( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - schema: GraphQLSchema, - document: Document, - errors: [GraphQLError] - ) { - dispatchQueue.async(group: dispatchGroup) { - self.instrumentation.queryValidation( - processId: processId, - threadId: threadId, - started: started, - finished: finished, - schema: schema, - document: document, - errors: errors - ) - } - } - - public func operationExecution( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - schema: GraphQLSchema, - document: Document, - rootValue: Any, - eventLoopGroup: EventLoopGroup, - variableValues: [String: Map], - operation: OperationDefinition?, - errors: [GraphQLError], - result: Map - ) { - dispatchQueue.async(group: dispatchGroup) { - self.instrumentation.operationExecution( - processId: processId, - threadId: threadId, - started: started, - finished: finished, - schema: schema, - document: document, - rootValue: rootValue, - eventLoopGroup: eventLoopGroup, - variableValues: variableValues, - operation: operation, - errors: errors, - result: result - ) - } - } - - public func fieldResolution( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - source: Any, - args: Map, - eventLoopGroup: EventLoopGroup, - info: GraphQLResolveInfo, - result: Result, Error> - ) { - dispatchQueue.async(group: dispatchGroup) { - self.instrumentation.fieldResolution( - processId: processId, - threadId: threadId, - started: started, - finished: finished, - source: source, - args: args, - eventLoopGroup: eventLoopGroup, - info: info, - result: result - ) - } - } -} diff --git a/Sources/GraphQL/Instrumentation/Instrumentation.swift b/Sources/GraphQL/Instrumentation/Instrumentation.swift deleted file mode 100644 index 1a315ab0..00000000 --- a/Sources/GraphQL/Instrumentation/Instrumentation.swift +++ /dev/null @@ -1,126 +0,0 @@ -import Dispatch -import Foundation -import NIO - -/// Provides the capability to instrument the execution steps of a GraphQL query. -/// -/// A working implementation of `now` is also provided by default. -public protocol Instrumentation { - var now: DispatchTime { get } - - func queryParsing( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - source: Source, - result: Result - ) - - func queryValidation( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - schema: GraphQLSchema, - document: Document, - errors: [GraphQLError] - ) - - func operationExecution( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - schema: GraphQLSchema, - document: Document, - rootValue: Any, - eventLoopGroup: EventLoopGroup, - variableValues: [String: Map], - operation: OperationDefinition?, - errors: [GraphQLError], - result: Map - ) - - func fieldResolution( - processId: Int, - threadId: Int, - started: DispatchTime, - finished: DispatchTime, - source: Any, - args: Map, - eventLoopGroup: EventLoopGroup, - info: GraphQLResolveInfo, - result: Result, Error> - ) -} - -public extension Instrumentation { - var now: DispatchTime { - return DispatchTime.now() - } -} - -func threadId() -> Int { - #if os(Linux) || os(Android) - return Int(pthread_self()) - #else - return Int(pthread_mach_thread_np(pthread_self())) - #endif -} - -func processId() -> Int { - return Int(getpid()) -} - -/// Does nothing -public let NoOpInstrumentation: Instrumentation = noOpInstrumentation() - -struct noOpInstrumentation: Instrumentation { - public let now = DispatchTime(uptimeNanoseconds: 0) - public func queryParsing( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - source _: Source, - result _: Result - ) {} - - public func queryValidation( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - schema _: GraphQLSchema, - document _: Document, - errors _: [GraphQLError] - ) {} - - public func operationExecution( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - schema _: GraphQLSchema, - document _: Document, - rootValue _: Any, - eventLoopGroup _: EventLoopGroup, - variableValues _: [String: Map], - operation _: OperationDefinition?, - errors _: [GraphQLError], - result _: Map - ) {} - - public func fieldResolution( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - source _: Any, - args _: Map, - eventLoopGroup _: EventLoopGroup, - info _: GraphQLResolveInfo, - result _: Result, Error> - ) {} -} diff --git a/Sources/GraphQL/Language/Parser.swift b/Sources/GraphQL/Language/Parser.swift index 87f9432e..90ef4e29 100644 --- a/Sources/GraphQL/Language/Parser.swift +++ b/Sources/GraphQL/Language/Parser.swift @@ -3,12 +3,10 @@ * Throws GraphQLError if a syntax error is encountered. */ public func parse( - instrumentation: Instrumentation = NoOpInstrumentation, source: String, noLocation: Bool = false ) throws -> Document { return try parse( - instrumentation: instrumentation, source: Source(body: source), noLocation: noLocation ) @@ -19,32 +17,14 @@ public func parse( * Throws GraphQLError if a syntax error is encountered. */ public func parse( - instrumentation: Instrumentation = NoOpInstrumentation, source: Source, noLocation: Bool = false ) throws -> Document { - let started = instrumentation.now do { let lexer = createLexer(source: source, noLocation: noLocation) let document = try parseDocument(lexer: lexer) - instrumentation.queryParsing( - processId: processId(), - threadId: threadId(), - started: started, - finished: instrumentation.now, - source: source, - result: .success(document) - ) return document } catch let error as GraphQLError { - instrumentation.queryParsing( - processId: processId(), - threadId: threadId(), - started: started, - finished: instrumentation.now, - source: source, - result: .failure(error) - ) throw error } } diff --git a/Sources/GraphQL/Map/AnyCoder.swift b/Sources/GraphQL/Map/AnyCoder.swift index dcab5052..debf273d 100644 --- a/Sources/GraphQL/Map/AnyCoder.swift +++ b/Sources/GraphQL/Map/AnyCoder.swift @@ -41,7 +41,6 @@ open class AnyEncoder { public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0) /// Produce Any with dictionary keys sorted in lexicographic order. - @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) } @@ -57,7 +56,6 @@ open class AnyEncoder { case millisecondsSince1970 /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) case iso8601 /// Encode the `Date` as a string formatted by the given formatter. @@ -1103,7 +1101,6 @@ open class AnyDecoder { case millisecondsSince1970 /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) case iso8601 /// Decode the `Date` as a string parsed by the given formatter. @@ -3258,7 +3255,6 @@ private struct _AnyKey: CodingKey { //===----------------------------------------------------------------------===// // NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled against the latest SDK (w/ ISO8601DateFormatter), but linked against whichever Foundation the user has. ISO8601DateFormatter might not exist, so we better not hit this code path on an older OS. -@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) private var _iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = .withInternetDateTime diff --git a/Sources/GraphQL/Map/GraphQLJSONEncoder.swift b/Sources/GraphQL/Map/GraphQLJSONEncoder.swift index a938ee4f..cb6029a7 100644 --- a/Sources/GraphQL/Map/GraphQLJSONEncoder.swift +++ b/Sources/GraphQL/Map/GraphQLJSONEncoder.swift @@ -39,7 +39,6 @@ open class GraphQLJSONEncoder { public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0) /// Produce JSON with dictionary keys sorted in lexicographic order. - @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) /// By default slashes get escaped ("/" → "\/", "http://apple.com/" → "http:\/\/apple.com\/") @@ -61,7 +60,6 @@ open class GraphQLJSONEncoder { case millisecondsSince1970 /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) case iso8601 /// Encode the `Date` as a string formatted by the given formatter. @@ -1298,7 +1296,6 @@ internal struct _JSONKey: CodingKey { //===----------------------------------------------------------------------===// // NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled against the latest SDK (w/ ISO8601DateFormatter), but linked against whichever Foundation the user has. ISO8601DateFormatter might not exist, so we better not hit this code path on an older OS. -@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) private var _iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = .withInternetDateTime diff --git a/Sources/GraphQL/Map/MapCoder.swift b/Sources/GraphQL/Map/MapCoder.swift index aabcac69..cc334dda 100644 --- a/Sources/GraphQL/Map/MapCoder.swift +++ b/Sources/GraphQL/Map/MapCoder.swift @@ -49,7 +49,6 @@ open class MapEncoder { public static let prettyPrinted = OutputFormatting(rawValue: 1 << 0) /// Produce Map with dictionary keys sorted in lexicographic order. - @available(macOS 10.13, iOS 11.0, watchOS 4.0, tvOS 11.0, *) public static let sortedKeys = OutputFormatting(rawValue: 1 << 1) } @@ -65,7 +64,6 @@ open class MapEncoder { case millisecondsSince1970 /// Encode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) case iso8601 /// Encode the `Date` as a string formatted by the given formatter. @@ -1111,7 +1109,6 @@ open class MapDecoder { case millisecondsSince1970 /// Decode the `Date` as an ISO-8601-formatted string (in RFC 3339 format). - @available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) case iso8601 /// Decode the `Date` as a string parsed by the given formatter. @@ -3266,7 +3263,6 @@ private struct _MapKey: CodingKey { //===----------------------------------------------------------------------===// // NOTE: This value is implicitly lazy and _must_ be lazy. We're compiled against the latest SDK (w/ ISO8601DateFormatter), but linked against whichever Foundation the user has. ISO8601DateFormatter might not exist, so we better not hit this code path on an older OS. -@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *) private var _iso8601Formatter: ISO8601DateFormatter = { let formatter = ISO8601DateFormatter() formatter.formatOptions = .withInternetDateTime diff --git a/Sources/GraphQL/Map/Number.swift b/Sources/GraphQL/Map/Number.swift index f711cb27..d0459c8e 100644 --- a/Sources/GraphQL/Map/Number.swift +++ b/Sources/GraphQL/Map/Number.swift @@ -35,13 +35,11 @@ public struct Number: Sendable { storageType = .bool } - @available(OSX 10.5, *) public init(_ value: Int) { _number = NSNumber(value: value) storageType = .int } - @available(OSX 10.5, *) public init(_ value: UInt) { _number = NSNumber(value: value) storageType = .int @@ -101,12 +99,10 @@ public struct Number: Sendable { return _number.boolValue } - @available(OSX 10.5, *) public var intValue: Int { return _number.intValue } - @available(OSX 10.5, *) public var uintValue: UInt { return _number.uintValue } diff --git a/Sources/GraphQL/Subscription/EventStream.swift b/Sources/GraphQL/Subscription/EventStream.swift deleted file mode 100644 index 5ff7ad89..00000000 --- a/Sources/GraphQL/Subscription/EventStream.swift +++ /dev/null @@ -1,75 +0,0 @@ -/// Abstract event stream class - Should be overridden for actual implementations -open class EventStream { - public init() {} - /// Template method for mapping an event stream to a new generic type - MUST be overridden by - /// implementing types. - open func map(_: @escaping (Element) throws -> To) -> EventStream { - fatalError("This function should be overridden by implementing classes") - } -} - -/// Event stream that wraps an `AsyncThrowingStream` from Swift's standard concurrency system. -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) -public class ConcurrentEventStream: EventStream { - public let stream: AsyncThrowingStream - - public init(_ stream: AsyncThrowingStream) { - self.stream = stream - } - - /// Performs the closure on each event in the current stream and returns a stream of the - /// results. - /// - Parameter closure: The closure to apply to each event in the stream - /// - Returns: A stream of the results - override open func map(_ closure: @escaping (Element) throws -> To) - -> ConcurrentEventStream { - let newStream = stream.mapStream(closure) - return ConcurrentEventStream(newStream) - } -} - -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) -extension AsyncThrowingStream { - func mapStream(_ closure: @escaping (Element) throws -> To) - -> AsyncThrowingStream { - return AsyncThrowingStream { continuation in - let task = Task { - do { - for try await event in self { - let newEvent = try closure(event) - continuation.yield(newEvent) - } - continuation.finish() - } catch { - continuation.finish(throwing: error) - } - } - - continuation.onTermination = { @Sendable reason in - task.cancel() - } - } - } - - func filterStream(_ isIncluded: @escaping (Element) throws -> Bool) - -> AsyncThrowingStream { - return AsyncThrowingStream { continuation in - let task = Task { - do { - for try await event in self { - if try isIncluded(event) { - continuation.yield(event) - } - } - continuation.finish() - } catch { - continuation.finish(throwing: error) - } - } - - continuation.onTermination = { @Sendable _ in - task.cancel() - } - } - } -} diff --git a/Sources/GraphQL/Subscription/Subscribe.swift b/Sources/GraphQL/Subscription/Subscribe.swift index 8236b3d5..82f7b842 100644 --- a/Sources/GraphQL/Subscription/Subscribe.swift +++ b/Sources/GraphQL/Subscription/Subscribe.swift @@ -1,4 +1,3 @@ -import NIO import OrderedCollections /** @@ -25,56 +24,65 @@ 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 { - let sourceFuture = createSourceEventStream( +) async throws -> SubscriptionResult { + let sourceResult = try await 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 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 - ) + if let sourceStream = sourceResult.stream { + // We must create a new AsyncSequence because AsyncSequence.map requires a concrete type + // (which we cannot know), + // and we need the result to be a concrete type. + let subscriptionStream = AsyncThrowingStream { continuation in + let task = Task { + do { + for try await eventPayload in sourceStream { + // 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. + let newEvent = try await execute( + queryStrategy: queryStrategy, + mutationStrategy: mutationStrategy, + subscriptionStrategy: subscriptionStrategy, + schema: schema, + documentAST: documentAST, + rootValue: eventPayload, + context: context, + variableValues: variableValues, + operationName: operationName + ) + continuation.yield(newEvent) + } + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + + continuation.onTermination = { @Sendable reason in + task.cancel() } - return SubscriptionResult(stream: subscriptionStream, errors: sourceResult.errors) - } else { - return SubscriptionResult(errors: sourceResult.errors) } + return SubscriptionResult(stream: subscriptionStream, errors: sourceResult.errors) + } else { + return SubscriptionResult(errors: sourceResult.errors) } } @@ -109,17 +117,13 @@ 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 { - let executeStarted = instrumentation.now - +) async throws -> SourceEventStreamResult { do { // If a valid context cannot be created due to incorrect arguments, // this will throw an error. @@ -127,43 +131,24 @@ func createSourceEventStream( 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) + return try await executeSubscription(context: exeContext) } 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])) + return SourceEventStreamResult(errors: [error]) } catch { - return eventLoopGroup.next() - .makeSucceededFuture(SourceEventStreamResult(errors: [GraphQLError(error)])) + return SourceEventStreamResult(errors: [GraphQLError(error)]) } } func executeSubscription( - context: ExecutionContext, - eventLoopGroup: EventLoopGroup -) throws -> EventLoopFuture { + context: ExecutionContext +) async throws -> SourceEventStreamResult { // Get the first node let type = try getOperationRootType(schema: context.schema, operation: context.operation) var inputFields: OrderedDictionary = [:] @@ -233,17 +218,16 @@ func executeSubscription( // Get the resolve func, regardless of if its result is normal // or abrupt (error). - let resolvedFutureOrError = resolveOrError( + let resolvedOrError = await resolveOrError( resolve: resolve, source: context.rootValue, args: args, context: contextValue, - eventLoopGroup: eventLoopGroup, info: info ) - let resolvedFuture: Future - switch resolvedFutureOrError { + let resolved: Any? + switch resolvedOrError { case let .failure(error): if let graphQLError = error as? GraphQLError { throw graphQLError @@ -251,27 +235,25 @@ func executeSubscription( throw GraphQLError(error) } case let .success(success): - resolvedFuture = success + resolved = 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 { - 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. Received: '\(resolvedObj)'" - ), - ]) - } + 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? any AsyncSequence { + 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 an AsyncSequence. Received: '\(resolvedObj)'" + ), + ]) } } @@ -280,10 +262,10 @@ func executeSubscription( // checking. Normal resolvers for subscription fields should handle type casting, same as resolvers // for query fields. struct SourceEventStreamResult { - public let stream: EventStream? + public let stream: (any AsyncSequence)? public let errors: [GraphQLError] - public init(stream: EventStream? = nil, errors: [GraphQLError] = []) { + public init(stream: (any AsyncSequence)? = nil, errors: [GraphQLError] = []) { self.stream = stream self.errors = errors } diff --git a/Sources/GraphQL/Type/Definition.swift b/Sources/GraphQL/Type/Definition.swift index 7f2c0924..409d8344 100644 --- a/Sources/GraphQL/Type/Definition.swift +++ b/Sources/GraphQL/Type/Definition.swift @@ -1,5 +1,4 @@ import Foundation -import NIO import OrderedCollections /** @@ -414,13 +413,11 @@ public enum TypeResolveResult { public typealias GraphQLTypeResolve = ( _ value: Any, - _ eventLoopGroup: EventLoopGroup, _ info: GraphQLResolveInfo ) throws -> TypeResolveResultRepresentable public typealias GraphQLIsTypeOf = ( _ source: Any, - _ eventLoopGroup: EventLoopGroup, _ info: GraphQLResolveInfo ) throws -> Bool @@ -428,9 +425,8 @@ public typealias GraphQLFieldResolve = ( _ source: Any, _ args: Map, _ context: Any, - _ eventLoopGroup: EventLoopGroup, _ info: GraphQLResolveInfo -) throws -> Future +) async throws -> Any? public typealias GraphQLFieldResolveInput = ( _ source: Any, @@ -511,9 +507,9 @@ public struct GraphQLField { self.description = description self.astNode = astNode - self.resolve = { source, args, context, eventLoopGroup, info in + self.resolve = { source, args, context, info in let result = try resolve(source, args, context, info) - return eventLoopGroup.next().makeSucceededFuture(result) + return result } subscribe = nil } diff --git a/Sources/GraphQL/Type/Introspection.swift b/Sources/GraphQL/Type/Introspection.swift index 9841851d..89c2cc2f 100644 --- a/Sources/GraphQL/Type/Introspection.swift +++ b/Sources/GraphQL/Type/Introspection.swift @@ -1,4 +1,3 @@ -import NIO let __Schema = try! GraphQLObjectType( name: "__Schema", @@ -492,8 +491,8 @@ let SchemaMetaFieldDef = GraphQLFieldDefinition( name: "__schema", type: GraphQLNonNull(__Schema), description: "Access the current type schema of this server.", - resolve: { _, _, _, eventLoopGroup, info in - eventLoopGroup.next().makeSucceededFuture(info.schema) + resolve: { _, _, _, info in + info.schema } ) @@ -507,9 +506,9 @@ let TypeMetaFieldDef = GraphQLFieldDefinition( type: GraphQLNonNull(GraphQLString) ), ], - resolve: { _, arguments, _, eventLoopGroup, info in + resolve: { _, arguments, _, info in let name = arguments["name"].string! - return eventLoopGroup.next().makeSucceededFuture(info.schema.getType(name: name)) + return info.schema.getType(name: name) } ) @@ -517,8 +516,8 @@ let TypeNameMetaFieldDef = GraphQLFieldDefinition( name: "__typename", type: GraphQLNonNull(GraphQLString), description: "The name of the current Object type at runtime.", - resolve: { _, _, _, eventLoopGroup, info in - eventLoopGroup.next().makeSucceededFuture(info.parentType.name) + resolve: { _, _, _, info in + info.parentType.name } ) diff --git a/Sources/GraphQL/Utilities/NIO+Extensions.swift b/Sources/GraphQL/Utilities/NIO+Extensions.swift deleted file mode 100644 index 2131551a..00000000 --- a/Sources/GraphQL/Utilities/NIO+Extensions.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// NIO+Extensions.swift -// GraphQL -// -// Created by Jeff Seibert on 3/9/18. -// - -import Foundation -import NIO -import OrderedCollections - -public typealias Future = EventLoopFuture - -public extension Collection { - func flatten(on eventLoopGroup: EventLoopGroup) -> Future<[T]> where Element == Future { - return Future.whenAllSucceed(Array(self), on: eventLoopGroup.next()) - } -} - -extension Dictionary where Value: FutureType { - func flatten(on eventLoopGroup: EventLoopGroup) -> Future<[Key: Value.Expectation]> { - // create array of futures with (key,value) tuple - let futures: [Future<(Key, Value.Expectation)>] = map { element in - element.value.map(file: #file, line: #line) { (key: element.key, value: $0) } - } - // when all futures have succeeded convert tuple array back to dictionary - return EventLoopFuture.whenAllSucceed(futures, on: eventLoopGroup.next()).map { - .init(uniqueKeysWithValues: $0) - } - } -} - -extension OrderedDictionary where Value: FutureType { - func flatten(on eventLoopGroup: EventLoopGroup) - -> Future> - { - let keys = self.keys - // create array of futures with (key,value) tuple - let futures: [Future<(Key, Value.Expectation)>] = map { element in - element.value.map(file: #file, line: #line) { (key: element.key, value: $0) } - } - // when all futures have succeeded convert tuple array back to dictionary - return EventLoopFuture.whenAllSucceed(futures, on: eventLoopGroup.next()) - .map { unorderedResult in - var result: OrderedDictionary = [:] - for key in keys { - // Unwrap is guaranteed because keys are from original dictionary and maps - // preserve all elements - result[key] = unorderedResult.first(where: { $0.0 == key })!.1 - } - return result - } - } -} - -public protocol FutureType { - associatedtype Expectation - func whenSuccess(_ callback: @escaping @Sendable (Expectation) -> Void) - func whenFailure(_ callback: @escaping @Sendable (Error) -> Void) - func map( - file: StaticString, - line: UInt, - _ callback: @escaping (Expectation) -> (NewValue) - ) -> EventLoopFuture -} - -extension Future: FutureType { - public typealias Expectation = Value -} - -// Copied from https://github.com/vapor/async-kit/blob/e2f741640364c1d271405da637029ea6a33f754e/Sources/AsyncKit/EventLoopFuture/Future%2BTry.swift -// in order to avoid full package dependency. -public extension EventLoopFuture { - func tryFlatMap( - file _: StaticString = #file, line _: UInt = #line, - _ callback: @escaping (Value) throws -> EventLoopFuture - ) -> EventLoopFuture { - /// When the current `EventLoopFuture` is fulfilled, run the provided callback, - /// which will provide a new `EventLoopFuture`. - /// - /// This allows you to dynamically dispatch new asynchronous tasks as phases in a - /// longer series of processing steps. Note that you can use the results of the - /// current `EventLoopFuture` when determining how to dispatch the next operation. - /// - /// The key difference between this method and the regular `flatMap` is error handling. - /// - /// With `tryFlatMap`, the provided callback _may_ throw Errors, causing the returned - /// `EventLoopFuture` - /// to report failure immediately after the completion of the original `EventLoopFuture`. - flatMap { [eventLoop] value in - do { - return try callback(value) - } catch { - return eventLoop.makeFailedFuture(error) - } - } - } -} diff --git a/Sources/GraphQL/Validation/Validate.swift b/Sources/GraphQL/Validation/Validate.swift index 362d4ad4..dfa5f651 100644 --- a/Sources/GraphQL/Validation/Validate.swift +++ b/Sources/GraphQL/Validation/Validate.swift @@ -4,17 +4,15 @@ /// an empty array if no errors were encountered and the document is valid. /// /// - Parameters: -/// - instrumentation: The instrumentation implementation to call during the parsing, validating, /// execution, and field resolution stages. /// - schema: The GraphQL type system to use when validating and executing a query. /// - ast: A GraphQL document representing the requested operation. /// - Returns: zero or more errors public func validate( - instrumentation: Instrumentation = NoOpInstrumentation, schema: GraphQLSchema, ast: Document ) -> [GraphQLError] { - return validate(instrumentation: instrumentation, schema: schema, ast: ast, rules: []) + return validate(schema: schema, ast: ast, rules: []) } /** @@ -31,24 +29,13 @@ public func validate( * GraphQLErrors, or Arrays of GraphQLErrors when invalid. */ public func validate( - instrumentation: Instrumentation = NoOpInstrumentation, schema: GraphQLSchema, ast: Document, rules: [(ValidationContext) -> Visitor] ) -> [GraphQLError] { - let started = instrumentation.now let typeInfo = TypeInfo(schema: schema) let rules = rules.isEmpty ? specifiedRules : rules let errors = visit(usingRules: rules, schema: schema, typeInfo: typeInfo, documentAST: ast) - instrumentation.queryValidation( - processId: processId(), - threadId: threadId(), - started: started, - finished: instrumentation.now, - schema: schema, - document: ast, - errors: errors - ) return errors } diff --git a/Tests/GraphQLTests/ExecutionTests/OneOfTests.swift b/Tests/GraphQLTests/ExecutionTests/OneOfTests.swift index 1b6a50d9..8c4bf25b 100644 --- a/Tests/GraphQLTests/ExecutionTests/OneOfTests.swift +++ b/Tests/GraphQLTests/ExecutionTests/OneOfTests.swift @@ -1,13 +1,10 @@ @testable import GraphQL -import NIO import XCTest class OneOfTests: XCTestCase { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - // MARK: OneOf Input Objects - func testAcceptsAGoodDefaultValue() throws { + func testAcceptsAGoodDefaultValue() async throws { let query = """ query ($input: TestInputObject! = {a: "abc"}) { test(input: $input) { @@ -16,11 +13,10 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual( result, GraphQLResult(data: [ @@ -32,7 +28,7 @@ class OneOfTests: XCTestCase { ) } - func testRejectsABadDefaultValue() throws { + func testRejectsABadDefaultValue() async throws { let query = """ query ($input: TestInputObject! = {a: "abc", b: 123}) { test(input: $input) { @@ -41,11 +37,10 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result.errors.count, 1) XCTAssertEqual( result.errors[0].message, @@ -53,7 +48,7 @@ class OneOfTests: XCTestCase { ) } - func testAcceptsAGoodVariable() throws { + func testAcceptsAGoodVariable() async throws { let query = """ query ($input: TestInputObject!) { test(input: $input) { @@ -62,12 +57,11 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), request: query, - eventLoopGroup: eventLoopGroup, variableValues: ["input": ["a": "abc"]] - ).wait() + ) XCTAssertEqual( result, GraphQLResult(data: [ @@ -79,7 +73,7 @@ class OneOfTests: XCTestCase { ) } - func testAcceptsAGoodVariableWithAnUndefinedKey() throws { + func testAcceptsAGoodVariableWithAnUndefinedKey() async throws { let query = """ query ($input: TestInputObject!) { test(input: $input) { @@ -88,12 +82,11 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), request: query, - eventLoopGroup: eventLoopGroup, variableValues: ["input": ["a": "abc", "b": .undefined]] - ).wait() + ) XCTAssertEqual( result, GraphQLResult(data: [ @@ -105,7 +98,7 @@ class OneOfTests: XCTestCase { ) } - func testRejectsAVariableWithMultipleNonNullKeys() throws { + func testRejectsAVariableWithMultipleNonNullKeys() async throws { let query = """ query ($input: TestInputObject!) { test(input: $input) { @@ -114,12 +107,11 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), request: query, - eventLoopGroup: eventLoopGroup, variableValues: ["input": ["a": "abc", "b": 123]] - ).wait() + ) XCTAssertEqual(result.errors.count, 1) XCTAssertEqual( result.errors[0].message, @@ -130,7 +122,7 @@ class OneOfTests: XCTestCase { ) } - func testRejectsAVariableWithMultipleNullableKeys() throws { + func testRejectsAVariableWithMultipleNullableKeys() async throws { let query = """ query ($input: TestInputObject!) { test(input: $input) { @@ -139,12 +131,11 @@ class OneOfTests: XCTestCase { } } """ - let result = try graphql( + let result = try await graphql( schema: getSchema(), request: query, - eventLoopGroup: eventLoopGroup, variableValues: ["input": ["a": "abc", "b": .null]] - ).wait() + ) XCTAssertEqual(result.errors.count, 1) XCTAssertEqual( result.errors[0].message, @@ -163,7 +154,7 @@ func getSchema() throws -> GraphQLSchema { "a": GraphQLField(type: GraphQLString), "b": GraphQLField(type: GraphQLInt), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is TestObject } ) diff --git a/Tests/GraphQLTests/FieldExecutionStrategyTests/FieldExecutionStrategyTests.swift b/Tests/GraphQLTests/FieldExecutionStrategyTests/FieldExecutionStrategyTests.swift index cd021f3b..3caee2e0 100644 --- a/Tests/GraphQLTests/FieldExecutionStrategyTests/FieldExecutionStrategyTests.swift +++ b/Tests/GraphQLTests/FieldExecutionStrategyTests/FieldExecutionStrategyTests.swift @@ -1,8 +1,9 @@ import Dispatch @testable import GraphQL -import NIO import XCTest +let queue = DispatchQueue(label: "testQueue") + class FieldExecutionStrategyTests: XCTestCase { enum StrategyError: Error { case exampleError(msg: String) @@ -14,20 +15,18 @@ class FieldExecutionStrategyTests: XCTestCase { fields: [ "sleep": GraphQLField( type: GraphQLString, - resolve: { _, _, _, eventLoopGroup, _ in - eventLoopGroup.next().makeSucceededVoidFuture().map { - Thread.sleep(forTimeInterval: 0.1) - return "z" - } + resolve: { _, _, _, _ in + Thread.sleep(forTimeInterval: 0.1) + return "z" } ), "bang": GraphQLField( type: GraphQLString, - resolve: { (_, _, _, _, info: GraphQLResolveInfo) in + resolve: { (_, _, _, info: GraphQLResolveInfo) in let group = DispatchGroup() group.enter() - DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) { + queue.asyncAfter(wallDeadline: .now() + 0.1) { group.leave() } @@ -40,19 +39,19 @@ class FieldExecutionStrategyTests: XCTestCase { ), "futureBang": GraphQLField( type: GraphQLString, - resolve: { (_, _, _, eventLoopGroup, info: GraphQLResolveInfo) in + resolve: { (_, _, _, info: GraphQLResolveInfo) in let g = DispatchGroup() g.enter() - DispatchQueue.global().asyncAfter(wallDeadline: .now() + 0.1) { + queue.asyncAfter(wallDeadline: .now() + 0.1) { g.leave() } g.wait() - return eventLoopGroup.next().makeFailedFuture(StrategyError.exampleError( + throw StrategyError.exampleError( msg: "\(info.fieldName): \(info.path.elements.last!)" - )) + ) } ), ] @@ -184,9 +183,10 @@ class FieldExecutionStrategyTests: XCTestCase { ), ] - func timing(_ block: @autoclosure () throws -> T) throws -> (value: T, seconds: Double) { + func timing(_ block: @autoclosure () async throws -> T) async throws + -> (value: T, seconds: Double) { let start = DispatchTime.now() - let value = try block() + let value = try await block() let nanoseconds = DispatchTime.now().uptimeNanoseconds - start.uptimeNanoseconds let seconds = Double(nanoseconds) / 1_000_000_000 return ( @@ -195,67 +195,52 @@ class FieldExecutionStrategyTests: XCTestCase { ) } - private var eventLoopGroup: EventLoopGroup! - - override func setUp() { - eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - } - - override func tearDown() { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - - func testSerialFieldExecutionStrategyWithSingleField() throws { - let result = try timing(graphql( + func testSerialFieldExecutionStrategyWithSingleField() async throws { + let result = try await timing(await graphql( queryStrategy: SerialFieldExecutionStrategy(), schema: schema, - request: singleQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: singleQuery + )) XCTAssertEqual(result.value, singleExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testSerialFieldExecutionStrategyWithSingleFieldError() throws { - let result = try timing(graphql( + func testSerialFieldExecutionStrategyWithSingleFieldError() async throws { + let result = try await timing(await graphql( queryStrategy: SerialFieldExecutionStrategy(), schema: schema, - request: singleThrowsQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: singleThrowsQuery + )) XCTAssertEqual(result.value, singleThrowsExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testSerialFieldExecutionStrategyWithSingleFieldFailedFuture() throws { - let result = try timing(graphql( + func testSerialFieldExecutionStrategyWithSingleFieldFailedFuture() async throws { + let result = try await timing(await graphql( queryStrategy: SerialFieldExecutionStrategy(), schema: schema, - request: singleFailedFutureQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: singleFailedFutureQuery + )) XCTAssertEqual(result.value, singleFailedFutureExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testSerialFieldExecutionStrategyWithMultipleFields() throws { - let result = try timing(graphql( + func testSerialFieldExecutionStrategyWithMultipleFields() async throws { + let result = try await timing(await graphql( queryStrategy: SerialFieldExecutionStrategy(), schema: schema, - request: multiQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: multiQuery + )) XCTAssertEqual(result.value, multiExpected) // XCTAssertEqualWithAccuracy(1.0, result.seconds, accuracy: 0.5) } - func testSerialFieldExecutionStrategyWithMultipleFieldErrors() throws { - let result = try timing(graphql( + func testSerialFieldExecutionStrategyWithMultipleFieldErrors() async throws { + let result = try await timing(await graphql( queryStrategy: SerialFieldExecutionStrategy(), schema: schema, - request: multiThrowsQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: multiThrowsQuery + )) XCTAssertEqual(result.value.data, multiThrowsExpectedData) let resultErrors = result.value.errors XCTAssertEqual(resultErrors.count, multiThrowsExpectedErrors.count) @@ -265,46 +250,42 @@ class FieldExecutionStrategyTests: XCTestCase { // XCTAssertEqualWithAccuracy(1.0, result.seconds, accuracy: 0.5) } - func testConcurrentDispatchFieldExecutionStrategyWithSingleField() throws { - let result = try timing(graphql( - queryStrategy: ConcurrentDispatchFieldExecutionStrategy(), + func testConcurrentFieldExecutionStrategyWithSingleField() async throws { + let result = try await timing(await graphql( + queryStrategy: ConcurrentFieldExecutionStrategy(), schema: schema, - request: singleQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: singleQuery + )) XCTAssertEqual(result.value, singleExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testConcurrentDispatchFieldExecutionStrategyWithSingleFieldError() throws { - let result = try timing(graphql( - queryStrategy: ConcurrentDispatchFieldExecutionStrategy(), + func testConcurrentFieldExecutionStrategyWithSingleFieldError() async throws { + let result = try await timing(await graphql( + queryStrategy: ConcurrentFieldExecutionStrategy(), schema: schema, - request: singleThrowsQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: singleThrowsQuery + )) XCTAssertEqual(result.value, singleThrowsExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testConcurrentDispatchFieldExecutionStrategyWithMultipleFields() throws { - let result = try timing(graphql( - queryStrategy: ConcurrentDispatchFieldExecutionStrategy(), + func testConcurrentFieldExecutionStrategyWithMultipleFields() async throws { + let result = try await timing(await graphql( + queryStrategy: ConcurrentFieldExecutionStrategy(), schema: schema, - request: multiQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: multiQuery + )) XCTAssertEqual(result.value, multiExpected) // XCTAssertEqualWithAccuracy(0.1, result.seconds, accuracy: 0.25) } - func testConcurrentDispatchFieldExecutionStrategyWithMultipleFieldErrors() throws { - let result = try timing(graphql( - queryStrategy: ConcurrentDispatchFieldExecutionStrategy(), + func testConcurrentFieldExecutionStrategyWithMultipleFieldErrors() async throws { + let result = try await timing(await graphql( + queryStrategy: ConcurrentFieldExecutionStrategy(), schema: schema, - request: multiThrowsQuery, - eventLoopGroup: eventLoopGroup - ).wait()) + request: multiThrowsQuery + )) XCTAssertEqual(result.value.data, multiThrowsExpectedData) let resultErrors = result.value.errors XCTAssertEqual(resultErrors.count, multiThrowsExpectedErrors.count) diff --git a/Tests/GraphQLTests/HelloWorldTests/HelloWorldTests.swift b/Tests/GraphQLTests/HelloWorldTests/HelloWorldTests.swift index de6252ea..3131c600 100644 --- a/Tests/GraphQLTests/HelloWorldTests/HelloWorldTests.swift +++ b/Tests/GraphQLTests/HelloWorldTests/HelloWorldTests.swift @@ -1,5 +1,4 @@ @testable import GraphQL -import NIO import XCTest class HelloWorldTests: XCTestCase { @@ -17,32 +16,19 @@ class HelloWorldTests: XCTestCase { ) ) - func testHello() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - + func testHello() async throws { let query = "{ hello }" let expected = GraphQLResult(data: ["hello": "world"]) - let result = try graphql( + let result = try await graphql( schema: schema, - request: query, - eventLoopGroup: group - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testBoyhowdy() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - + func testBoyhowdy() async throws { let query = "{ boyhowdy }" let expected = GraphQLResult( @@ -54,30 +40,21 @@ class HelloWorldTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: schema, - request: query, - eventLoopGroup: group - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - @available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) func testHelloAsync() async throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "{ hello }" let expected = GraphQLResult(data: ["hello": "world"]) let result = try await graphql( schema: schema, - request: query, - eventLoopGroup: group + request: query ) XCTAssertEqual(result, expected) diff --git a/Tests/GraphQLTests/InputTests/InputTests.swift b/Tests/GraphQLTests/InputTests/InputTests.swift index fe66d461..0c9fd2b7 100644 --- a/Tests/GraphQLTests/InputTests/InputTests.swift +++ b/Tests/GraphQLTests/InputTests/InputTests.swift @@ -1,9 +1,8 @@ @testable import GraphQL -import NIO import XCTest class InputTests: XCTestCase { - func testArgsNonNullNoDefault() throws { + func testArgsNonNullNoDefault() async throws { struct Echo: Codable { let field1: String } @@ -20,7 +19,7 @@ class InputTests: XCTestCase { type: GraphQLNonNull(GraphQLString) ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -48,49 +47,44 @@ class InputTests: XCTestCase { types: [EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test basic functionality - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: "value1" - ) { - field1 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: "value1" + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": "value1", - ] - ).wait(), + } + """, + variableValues: [ + "field1": "value1", + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -99,77 +93,73 @@ class InputTests: XCTestCase { ) // Test providing null results in an error - XCTAssertTrue( - try graphql( - schema: schema, - request: """ - { - echo( - field1: null - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: null + ) { + field1 } - """, - eventLoopGroup: group - ).wait() - .errors.count > 0 + } + """ ) XCTAssertTrue( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result.errors.count > 0 + ) + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": .null, - ] - ).wait() - .errors.count > 0 + } + """, + variableValues: [ + "field1": .null, + ] + ) + XCTAssertTrue( + result.errors.count > 0 ) // Test not providing parameter results in an error - XCTAssertTrue( - try graphql( - schema: schema, - request: """ - { - echo { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo { + field1 } - """, - eventLoopGroup: group - ).wait() - .errors.count > 0 + } + """ ) XCTAssertTrue( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result.errors.count > 0 + ) + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait() - .errors.count > 0 + } + """, + variableValues: [:] + ) + XCTAssertTrue( + result.errors.count > 0 ) } - func testArgsNullNoDefault() throws { + func testArgsNullNoDefault() async throws { struct Echo: Codable { let field1: String? } @@ -186,7 +176,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -214,49 +204,44 @@ class InputTests: XCTestCase { types: [EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test basic functionality - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: "value1" - ) { - field1 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: "value1" + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": "value1", - ] - ).wait(), + } + """, + variableValues: [ + "field1": "value1", + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -265,43 +250,43 @@ class InputTests: XCTestCase { ) // Test providing null is accepted - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: null - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: null + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": .null, - ] - ).wait(), + } + """, + variableValues: [ + "field1": .null, + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, @@ -310,39 +295,39 @@ class InputTests: XCTestCase { ) // Test not providing parameter is accepted - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait(), + } + """, + variableValues: [:] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, @@ -351,7 +336,7 @@ class InputTests: XCTestCase { ) } - func testArgsNonNullDefault() throws { + func testArgsNonNullDefault() async throws { struct Echo: Codable { let field1: String } @@ -368,7 +353,7 @@ class InputTests: XCTestCase { type: GraphQLNonNull(GraphQLString) ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -397,49 +382,44 @@ class InputTests: XCTestCase { types: [EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test basic functionality - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: "value1" - ) { - field1 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: "value1" + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": "value1", - ] - ).wait(), + } + """, + variableValues: [ + "field1": "value1", + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -448,76 +428,74 @@ class InputTests: XCTestCase { ) // Test providing null results in an error - XCTAssertTrue( - try graphql( - schema: schema, - request: """ - { - echo( - field1: null - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: null + ) { + field1 } - """, - eventLoopGroup: group - ).wait() - .errors.count > 0 + } + """ ) XCTAssertTrue( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result.errors.count > 0 + ) + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": .null, - ] - ).wait() - .errors.count > 0 + } + """, + variableValues: [ + "field1": .null, + ] + ) + XCTAssertTrue( + result.errors.count > 0 ) // Test not providing parameter results in default - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "defaultValue1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String! = "defaultValue1") { - echo ( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String! = "defaultValue1") { + echo ( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait(), + } + """, + variableValues: [:] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "defaultValue1", @@ -526,26 +504,25 @@ class InputTests: XCTestCase { ) // Test variable doesn't get argument default - XCTAssertTrue( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo ( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo ( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait() - .errors.count > 0 + } + """, + variableValues: [:] + ) + XCTAssertTrue( + result.errors.count > 0 ) } - func testArgsNullDefault() throws { + func testArgsNullDefault() async throws { struct Echo: Codable { let field1: String? } @@ -562,7 +539,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -591,49 +568,44 @@ class InputTests: XCTestCase { types: [EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test basic functionality - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: "value1" - ) { - field1 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: "value1" + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String!) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String!) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": "value1", - ] - ).wait(), + } + """, + variableValues: [ + "field1": "value1", + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -642,43 +614,43 @@ class InputTests: XCTestCase { ) // Test providing null results in a null output - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo( - field1: null - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo( + field1: null + ) { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String) { - echo( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String) { + echo( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [ - "field1": .null, - ] - ).wait(), + } + """, + variableValues: [ + "field1": .null, + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, @@ -687,39 +659,39 @@ class InputTests: XCTestCase { ) // Test not providing parameter results in default - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo { - field1 - } + result = try await graphql( + schema: schema, + request: """ + { + echo { + field1 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "defaultValue1", ], ]) ) - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String = "defaultValue1") { - echo ( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String = "defaultValue1") { + echo ( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait(), + } + """, + variableValues: [:] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "defaultValue1", @@ -728,21 +700,21 @@ class InputTests: XCTestCase { ) // Test that nullable unprovided variables are coerced to null - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($field1: String) { - echo ( - field1: $field1 - ) { - field1 - } + result = try await graphql( + schema: schema, + request: """ + query echo($field1: String) { + echo ( + field1: $field1 + ) { + field1 } - """, - eventLoopGroup: group, - variableValues: [:] - ).wait(), + } + """, + variableValues: [:] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": .null, @@ -752,7 +724,7 @@ class InputTests: XCTestCase { } // Test that input objects parse as expected from non-null literals - func testInputNoNull() throws { + func testInputNoNull() async throws { struct Echo: Codable { let field1: String? let field2: String? @@ -798,7 +770,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -827,28 +799,23 @@ class InputTests: XCTestCase { types: [EchoInputType, EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test in arguments - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo(input:{ - field1: "value1", - field2: "value2", - }) { - field1 - field2 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo(input:{ + field1: "value1", + field2: "value2", + }) { + field1 + field2 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -858,25 +825,25 @@ class InputTests: XCTestCase { ) // Test in variables - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($input: EchoInput) { - echo(input: $input) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + query echo($input: EchoInput) { + echo(input: $input) { + field1 + field2 } - """, - eventLoopGroup: group, - variableValues: [ - "input": [ - "field1": "value1", - "field2": "value2", - ], - ] - ).wait(), + } + """, + variableValues: [ + "input": [ + "field1": "value1", + "field2": "value2", + ], + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -887,7 +854,7 @@ class InputTests: XCTestCase { } // Test that inputs parse as expected when null literals are present - func testInputParsingDefinedNull() throws { + func testInputParsingDefinedNull() async throws { struct Echo: Codable { let field1: String? let field2: String? @@ -933,7 +900,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -962,28 +929,23 @@ class InputTests: XCTestCase { types: [EchoInputType, EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test in arguments - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo(input:{ - field1: "value1", - field2: null, - }) { - field1 - field2 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo(input:{ + field1: "value1", + field2: null, + }) { + field1 + field2 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -993,25 +955,25 @@ class InputTests: XCTestCase { ) // Test in variables - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($input: EchoInput) { - echo(input: $input) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + query echo($input: EchoInput) { + echo(input: $input) { + field1 + field2 } - """, - eventLoopGroup: group, - variableValues: [ - "input": [ - "field1": "value1", - "field2": .null, - ], - ] - ).wait(), + } + """, + variableValues: [ + "input": [ + "field1": "value1", + "field2": .null, + ], + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1022,7 +984,7 @@ class InputTests: XCTestCase { } // Test that input objects parse as expected when there are missing fields with no default - func testInputParsingUndefined() throws { + func testInputParsingUndefined() async throws { struct Echo: Codable { let field1: String? let field2: String? @@ -1073,7 +1035,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -1102,27 +1064,22 @@ class InputTests: XCTestCase { types: [EchoInputType, EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Test in arguments - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo(input:{ - field1: "value1" - }) { - field1 - field2 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo(input:{ + field1: "value1" + }) { + field1 + field2 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1132,24 +1089,24 @@ class InputTests: XCTestCase { ) // Test in variables - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($input: EchoInput) { - echo(input: $input) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + query echo($input: EchoInput) { + echo(input: $input) { + field1 + field2 } - """, - eventLoopGroup: group, - variableValues: [ - "input": [ - "field1": "value1", - ], - ] - ).wait(), + } + """, + variableValues: [ + "input": [ + "field1": "value1", + ], + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1160,7 +1117,7 @@ class InputTests: XCTestCase { } // Test that input objects parse as expected when there are missing fields with defaults - func testInputParsingUndefinedWithDefault() throws { + func testInputParsingUndefinedWithDefault() async throws { struct Echo: Codable { let field1: String? let field2: String? @@ -1207,7 +1164,7 @@ class InputTests: XCTestCase { type: GraphQLString ), ], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Echo } ) @@ -1236,27 +1193,22 @@ class InputTests: XCTestCase { types: [EchoInputType, EchoOutputType] ) - let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - // Undefined with default gets default - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo(input:{ - field1: "value1" - }) { - field1 - field2 - } + var result = try await graphql( + schema: schema, + request: """ + { + echo(input:{ + field1: "value1" + }) { + field1 + field2 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1265,22 +1217,22 @@ class InputTests: XCTestCase { ]) ) // Null literal with default gets null - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - { - echo(input:{ - field1: "value1" - field2: null - }) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + { + echo(input:{ + field1: "value1" + field2: null + }) { + field1 + field2 } - """, - eventLoopGroup: group - ).wait(), + } + """ + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1291,24 +1243,24 @@ class InputTests: XCTestCase { // Test in variable // Undefined with default gets default - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($input: EchoInput) { - echo(input: $input) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + query echo($input: EchoInput) { + echo(input: $input) { + field1 + field2 } - """, - eventLoopGroup: group, - variableValues: [ - "input": [ - "field1": "value1", - ], - ] - ).wait(), + } + """, + variableValues: [ + "input": [ + "field1": "value1", + ], + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", @@ -1317,25 +1269,25 @@ class InputTests: XCTestCase { ]) ) // Null literal with default gets null - XCTAssertEqual( - try graphql( - schema: schema, - request: """ - query echo($input: EchoInput) { - echo(input: $input) { - field1 - field2 - } + result = try await graphql( + schema: schema, + request: """ + query echo($input: EchoInput) { + echo(input: $input) { + field1 + field2 } - """, - eventLoopGroup: group, - variableValues: [ - "input": [ - "field1": "value1", - "field2": .null, - ], - ] - ).wait(), + } + """, + variableValues: [ + "input": [ + "field1": "value1", + "field2": .null, + ], + ] + ) + XCTAssertEqual( + result, GraphQLResult(data: [ "echo": [ "field1": "value1", diff --git a/Tests/GraphQLTests/InstrumentationTests/InstrumentationTests.swift b/Tests/GraphQLTests/InstrumentationTests/InstrumentationTests.swift deleted file mode 100644 index 56a77b93..00000000 --- a/Tests/GraphQLTests/InstrumentationTests/InstrumentationTests.swift +++ /dev/null @@ -1,197 +0,0 @@ -import Dispatch -import Foundation -import GraphQL -import NIO -import XCTest - -class InstrumentationTests: XCTestCase, Instrumentation { - class MyRoot {} - class MyCtx {} - - var query = "query sayHello($name: String) { hello(name: $name) }" - var expectedResult: Map = [ - "data": [ - "hello": "bob", - ], - ] - var expectedThreadId = 0 - var expectedProcessId = 0 - var expectedRoot = MyRoot() - var expectedCtx = MyCtx() - var expectedOpVars: [String: Map] = ["name": "bob"] - var expectedOpName = "sayHello" - var queryParsingCalled = 0 - var queryValidationCalled = 0 - var operationExecutionCalled = 0 - var fieldResolutionCalled = 0 - - let schema = try! GraphQLSchema( - query: GraphQLObjectType( - name: "RootQueryType", - fields: [ - "hello": GraphQLField( - type: GraphQLString, - args: [ - "name": GraphQLArgument(type: GraphQLNonNull(GraphQLString)), - ], - resolve: { inputValue, _, _, _ in - print(type(of: inputValue)) - return nil - } -// resolve: { _, args, _, _ in return try! args["name"].asString() } - ), - ] - ) - ) - - override func setUp() { - expectedThreadId = 0 - expectedProcessId = 0 - queryParsingCalled = 0 - queryValidationCalled = 0 - operationExecutionCalled = 0 - fieldResolutionCalled = 0 - } - - func queryParsing( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - source _: Source, - result _: Result - ) { -// queryParsingCalled += 1 -// XCTAssertEqual(processId, expectedProcessId, "unexpected process id") -// XCTAssertEqual(threadId, expectedThreadId, "unexpected thread id") -// XCTAssertGreaterThan(finished, started) -// XCTAssertEqual(source.name, "GraphQL request") -// switch result { -// case .error(let e): -// XCTFail("unexpected error \(e)") -// case .result(let document): -// XCTAssertEqual(document.loc!.source.name, source.name) -// } -// XCTAssertEqual(source.name, "GraphQL request") - } - - func queryValidation( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - schema _: GraphQLSchema, - document _: Document, - errors _: [GraphQLError] - ) { - queryValidationCalled += 1 -// XCTAssertEqual(processId, expectedProcessId, "unexpected process id") -// XCTAssertEqual(threadId, expectedThreadId, "unexpected thread id") -// XCTAssertGreaterThan(finished, started) -// XCTAssertTrue(schema === self.schema) -// XCTAssertEqual(document.loc!.source.name, "GraphQL request") -// XCTAssertEqual(errors, []) - } - - func operationExecution( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - schema _: GraphQLSchema, - document _: Document, - rootValue _: Any, - eventLoopGroup _: EventLoopGroup, - variableValues _: [String: Map], - operation _: OperationDefinition?, - errors _: [GraphQLError], - result _: Map - ) { -// operationExecutionCalled += 1 -// XCTAssertEqual(processId, expectedProcessId, "unexpected process id") -// XCTAssertEqual(threadId, expectedThreadId, "unexpected thread id") -// XCTAssertGreaterThan(finished, started) -// XCTAssertTrue(schema === self.schema) -// XCTAssertEqual(document.loc?.source.name ?? "", "GraphQL request") -// XCTAssertTrue(rootValue as! MyRoot === expectedRoot) -// XCTAssertTrue(contextValue as! MyCtx === expectedCtx) -// XCTAssertEqual(variableValues, expectedOpVars) -// XCTAssertEqual(operation!.name!.value, expectedOpName) -// XCTAssertEqual(errors, []) -// XCTAssertEqual(result, expectedResult) - } - - func fieldResolution( - processId _: Int, - threadId _: Int, - started _: DispatchTime, - finished _: DispatchTime, - source _: Any, - args _: Map, - eventLoopGroup _: EventLoopGroup, - info _: GraphQLResolveInfo, - result _: Result, Error> - ) { - fieldResolutionCalled += 1 -// XCTAssertEqual(processId, expectedProcessId, "unexpected process id") -// XCTAssertEqual(threadId, expectedThreadId, "unexpected thread id") -// XCTAssertGreaterThan(finished, started) -// XCTAssertTrue(source as! MyRoot === expectedRoot) -// XCTAssertEqual(args, try! expectedOpVars.asMap()) -// XCTAssertTrue(context as! MyCtx === expectedCtx) -// switch result { -// case .error(let e): -// XCTFail("unexpected error \(e)") -// case .result(let r): -// XCTAssertEqual(r as! String, try! expectedResult["data"]["hello"].asString()) -// } - } - - func testInstrumentationCalls() throws { -// #if os(Linux) || os(Android) -// expectedThreadId = Int(pthread_self()) -// #else -// expectedThreadId = Int(pthread_mach_thread_np(pthread_self())) -// #endif -// expectedProcessId = Int(getpid()) -// let result = try graphql( -// instrumentation: self, -// schema: schema, -// request: query, -// rootValue: expectedRoot, -// contextValue: expectedCtx, -// variableValues: expectedOpVars, -// operationName: expectedOpName -// ) -// XCTAssertEqual(result, expectedResult) -// XCTAssertEqual(queryParsingCalled, 1) -// XCTAssertEqual(queryValidationCalled, 1) -// XCTAssertEqual(operationExecutionCalled, 1) -// XCTAssertEqual(fieldResolutionCalled, 1) - } - - func testDispatchQueueInstrumentationWrapper() throws { -// let dispatchGroup = DispatchGroup() -// #if os(Linux) || os(Android) -// expectedThreadId = Int(pthread_self()) -// #else -// expectedThreadId = Int(pthread_mach_thread_np(pthread_self())) -// #endif -// expectedProcessId = Int(getpid()) -// let result = try graphql( -// instrumentation: DispatchQueueInstrumentationWrapper(self, dispatchGroup: dispatchGroup), -// schema: schema, -// request: query, -// rootValue: expectedRoot, -// contextValue: expectedCtx, -// variableValues: expectedOpVars, -// operationName: expectedOpName -// ) -// dispatchGroup.wait() -// XCTAssertEqual(result, expectedResult) -// XCTAssertEqual(queryParsingCalled, 1) -// XCTAssertEqual(queryValidationCalled, 1) -// XCTAssertEqual(operationExecutionCalled, 1) -// XCTAssertEqual(fieldResolutionCalled, 1) - } -} diff --git a/Tests/GraphQLTests/StarWarsTests/StarWarsIntrospectionTests.swift b/Tests/GraphQLTests/StarWarsTests/StarWarsIntrospectionTests.swift index ff8b4b5b..3ce7a76e 100644 --- a/Tests/GraphQLTests/StarWarsTests/StarWarsIntrospectionTests.swift +++ b/Tests/GraphQLTests/StarWarsTests/StarWarsIntrospectionTests.swift @@ -1,15 +1,9 @@ -import NIO import XCTest @testable import GraphQL class StarWarsIntrospectionTests: XCTestCase { - func testIntrospectionTypeQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionTypeQuery() async throws { do { let query = "query IntrospectionTypeQuery {" + " __schema {" + @@ -73,23 +67,17 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } catch { print(error) } } - func testIntrospectionQueryTypeQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionQueryTypeQuery() async throws { let query = "query IntrospectionQueryTypeQuery {" + " __schema {" + " queryType {" + @@ -108,20 +96,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionDroidTypeQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionDroidTypeQuery() async throws { let query = "query IntrospectionDroidTypeQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -136,20 +118,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionDroidKindQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionDroidKindQuery() async throws { let query = "query IntrospectionDroidKindQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -166,20 +142,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionCharacterKindQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionCharacterKindQuery() async throws { let query = "query IntrospectionCharacterKindQuery {" + " __type(name: \"Character\") {" + " name" + @@ -196,20 +166,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionDroidFieldsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionDroidFieldsQuery() async throws { let query = "query IntrospectionDroidFieldsQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -275,20 +239,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionDroidNestedFieldsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionDroidNestedFieldsQuery() async throws { let query = "query IntrospectionDroidNestedFieldsQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -373,20 +331,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionFieldArgsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionFieldArgsQuery() async throws { let query = "query IntrospectionFieldArgsQuery {" + " __schema {" + " queryType {" + @@ -472,20 +424,14 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testIntrospectionDroidDescriptionQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testIntrospectionDroidDescriptionQuery() async throws { let query = "query IntrospectionDroidDescriptionQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -502,11 +448,10 @@ class StarWarsIntrospectionTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } } diff --git a/Tests/GraphQLTests/StarWarsTests/StarWarsQueryTests.swift b/Tests/GraphQLTests/StarWarsTests/StarWarsQueryTests.swift index 368436ad..d0053524 100644 --- a/Tests/GraphQLTests/StarWarsTests/StarWarsQueryTests.swift +++ b/Tests/GraphQLTests/StarWarsTests/StarWarsQueryTests.swift @@ -1,16 +1,9 @@ -import NIO import XCTest @testable import GraphQL class StarWarsQueryTests: XCTestCase { - func testHeroNameQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testHeroNameQuery() async throws { let query = """ query HeroNameQuery { hero { @@ -27,22 +20,15 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testHeroNameAndFriendsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testHeroNameAndFriendsQuery() async throws { let query = """ query HeroNameAndFriendsQuery { hero { @@ -69,22 +55,15 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testNestedQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testNestedQuery() async throws { let query = """ query NestedQuery { hero { @@ -139,20 +118,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testFetchLukeQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testFetchLukeQuery() async throws { let query = """ query FetchLukeQuery { @@ -170,20 +143,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testOptionalVariable() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testOptionalVariable() async throws { let query = """ query FetchHeroByEpisodeQuery($episode: Episode) { @@ -208,12 +175,11 @@ class StarWarsQueryTests: XCTestCase { ] ) - result = try graphql( + result = try await graphql( schema: starWarsSchema, request: query, - eventLoopGroup: eventLoopGroup, variableValues: params - ).wait() + ) XCTAssertEqual(result, expected) // or we can pass "EMPIRE" and expect Luke @@ -229,21 +195,15 @@ class StarWarsQueryTests: XCTestCase { ] ) - result = try graphql( + result = try await graphql( schema: starWarsSchema, request: query, - eventLoopGroup: eventLoopGroup, variableValues: params - ).wait() + ) XCTAssertEqual(result, expected) } - func testFetchSomeIDQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testFetchSomeIDQuery() async throws { let query = """ query FetchSomeIDQuery($someId: String!) { @@ -269,12 +229,11 @@ class StarWarsQueryTests: XCTestCase { ] ) - result = try graphql( + result = try await graphql( schema: starWarsSchema, request: query, - eventLoopGroup: eventLoopGroup, variableValues: params - ).wait() + ) XCTAssertEqual(result, expected) params = [ @@ -289,12 +248,11 @@ class StarWarsQueryTests: XCTestCase { ] ) - result = try graphql( + result = try await graphql( schema: starWarsSchema, request: query, - eventLoopGroup: eventLoopGroup, variableValues: params - ).wait() + ) XCTAssertEqual(result, expected) params = [ @@ -307,21 +265,15 @@ class StarWarsQueryTests: XCTestCase { ] ) - result = try graphql( + result = try await graphql( schema: starWarsSchema, request: query, - eventLoopGroup: eventLoopGroup, variableValues: params - ).wait() + ) XCTAssertEqual(result, expected) } - func testFetchLukeAliasedQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testFetchLukeAliasedQuery() async throws { let query = """ query FetchLukeAliasedQuery { @@ -339,20 +291,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testFetchLukeAndLeiaAliasedQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testFetchLukeAndLeiaAliasedQuery() async throws { let query = """ query FetchLukeAndLeiaAliasedQuery { @@ -376,20 +322,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testDuplicateFieldsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testDuplicateFieldsQuery() async throws { let query = """ query DuplicateFieldsQuery { @@ -417,20 +357,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testUseFragmentQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testUseFragmentQuery() async throws { let query = """ query UseFragmentQuery { @@ -460,20 +394,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testCheckTypeOfR2Query() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testCheckTypeOfR2Query() async throws { let query = """ query CheckTypeOfR2Query { @@ -493,21 +421,15 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testCheckTypeOfLukeQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testCheckTypeOfLukeQuery() async throws { let query = """ query CheckTypeOfLukeQuery { @@ -527,20 +449,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testSecretBackstoryQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testSecretBackstoryQuery() async throws { let query = """ query SecretBackstoryQuery { @@ -567,20 +483,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testSecretBackstoryListQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testSecretBackstoryListQuery() async throws { let query = """ query SecretBackstoryListQuery { @@ -633,20 +543,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testSecretBackstoryAliasQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testSecretBackstoryAliasQuery() async throws { let query = """ query SecretBackstoryAliasQuery { @@ -673,20 +577,14 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql( + let result = try await graphql( schema: starWarsSchema, - request: query, - eventLoopGroup: eventLoopGroup - ).wait() + request: query + ) XCTAssertEqual(result, expected) } - func testNonNullableFieldsQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - + func testNonNullableFieldsQuery() async throws { let A = try GraphQLObjectType( name: "A", fields: [:] @@ -762,19 +660,13 @@ class StarWarsQueryTests: XCTestCase { ] ) - let result = try graphql(schema: schema, request: query, eventLoopGroup: eventLoopGroup) - .wait() + let result = try await graphql(schema: schema, request: query) + XCTAssertEqual(result, expected) } - func testFieldOrderQuery() throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - - XCTAssertEqual(try graphql( + func testFieldOrderQuery() async throws { + var result = try await graphql( schema: starWarsSchema, request: """ query HeroNameQuery { @@ -783,9 +675,9 @@ class StarWarsQueryTests: XCTestCase { name } } - """, - eventLoopGroup: eventLoopGroup - ).wait(), GraphQLResult( + """ + ) + XCTAssertEqual(result, GraphQLResult( data: [ "hero": [ "id": "2001", @@ -794,7 +686,7 @@ class StarWarsQueryTests: XCTestCase { ] )) - XCTAssertNotEqual(try graphql( + result = try await graphql( schema: starWarsSchema, request: """ query HeroNameQuery { @@ -803,9 +695,9 @@ class StarWarsQueryTests: XCTestCase { name } } - """, - eventLoopGroup: eventLoopGroup - ).wait(), GraphQLResult( + """ + ) + XCTAssertNotEqual(result, GraphQLResult( data: [ "hero": [ "name": "R2-D2", diff --git a/Tests/GraphQLTests/StarWarsTests/StarWarsSchema.swift b/Tests/GraphQLTests/StarWarsTests/StarWarsSchema.swift index d9d70562..23841438 100644 --- a/Tests/GraphQLTests/StarWarsTests/StarWarsSchema.swift +++ b/Tests/GraphQLTests/StarWarsTests/StarWarsSchema.swift @@ -111,7 +111,7 @@ let CharacterInterface = try! GraphQLInterfaceType( description: "All secrets about their past." ), ] }, - resolveType: { character, _, _ in + resolveType: { character, _ in switch character { case is Human: return "Human" @@ -174,7 +174,7 @@ let HumanType = try! GraphQLObjectType( ), ], interfaces: [CharacterInterface], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Human } ) @@ -232,7 +232,7 @@ let DroidType = try! GraphQLObjectType( ), ], interfaces: [CharacterInterface], - isTypeOf: { source, _, _ in + isTypeOf: { source, _ in source is Droid } ) diff --git a/Tests/GraphQLTests/SubscriptionTests/SimplePubSub.swift b/Tests/GraphQLTests/SubscriptionTests/SimplePubSub.swift index 8a742378..fb198bfa 100644 --- a/Tests/GraphQLTests/SubscriptionTests/SimplePubSub.swift +++ b/Tests/GraphQLTests/SubscriptionTests/SimplePubSub.swift @@ -1,7 +1,6 @@ import GraphQL /// A very simple publish/subscriber used for testing -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) class SimplePubSub { private var subscribers: [Subscriber] @@ -21,8 +20,8 @@ class SimplePubSub { } } - func subscribe() -> ConcurrentEventStream { - let asyncStream = AsyncThrowingStream { continuation in + func subscribe() -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in let subscriber = Subscriber( callback: { newValue in continuation.yield(newValue) @@ -33,7 +32,6 @@ class SimplePubSub { ) subscribers.append(subscriber) } - return ConcurrentEventStream(asyncStream) } } diff --git a/Tests/GraphQLTests/SubscriptionTests/SubscriptionSchema.swift b/Tests/GraphQLTests/SubscriptionTests/SubscriptionSchema.swift index fb940c72..9a49c80c 100644 --- a/Tests/GraphQLTests/SubscriptionTests/SubscriptionSchema.swift +++ b/Tests/GraphQLTests/SubscriptionTests/SubscriptionSchema.swift @@ -1,5 +1,4 @@ @testable import GraphQL -import NIO // MARK: Types @@ -89,9 +88,6 @@ let EmailQueryType = try! GraphQLObjectType( // MARK: Test Helpers -let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) class EmailDb { var emails: [Email] let publisher: SimplePubSub @@ -121,28 +117,26 @@ class EmailDb { /// Returns the default email schema, with standard resolvers. func defaultSchema() throws -> GraphQLSchema { return try emailSchemaWithResolvers( - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + resolve: { emailAny, _, _, _ throws -> Any? in if let email = emailAny as? Email { - return eventLoopGroup.next().makeSucceededFuture(EmailEvent( + return EmailEvent( email: email, inbox: Inbox(emails: self.emails) - )) + ) } else { throw GraphQLError(message: "\(type(of: emailAny)) is not Email") } }, - subscribe: { _, args, _, eventLoopGroup, _ throws -> EventLoopFuture in + subscribe: { _, args, _, _ throws -> Any? in let priority = args["priority"].int ?? 0 - let filtered = self.publisher.subscribe().stream - .filterStream { emailAny throws in - if let email = emailAny as? Email { - return email.priority >= priority - } else { - return true - } + let filtered = self.publisher.subscribe().filter { emailAny throws in + if let email = emailAny as? Email { + return email.priority >= priority + } else { + return true } - return eventLoopGroup.next() - .makeSucceededFuture(ConcurrentEventStream(filtered)) + } + return filtered } ) } @@ -151,8 +145,8 @@ class EmailDb { func subscription( query: String, variableValues: [String: Map] = [:] - ) throws -> SubscriptionEventStream { - return try createSubscription( + ) async throws -> AsyncThrowingStream { + return try await createSubscription( schema: defaultSchema(), query: query, variableValues: variableValues @@ -191,20 +185,18 @@ func createSubscription( schema: GraphQLSchema, query: String, variableValues: [String: Map] = [:] -) throws -> SubscriptionEventStream { - let result = try graphqlSubscribe( +) async throws -> AsyncThrowingStream { + let result = try await graphqlSubscribe( queryStrategy: SerialFieldExecutionStrategy(), mutationStrategy: SerialFieldExecutionStrategy(), subscriptionStrategy: SerialFieldExecutionStrategy(), - instrumentation: NoOpInstrumentation, schema: schema, request: query, rootValue: (), context: (), - eventLoopGroup: eventLoopGroup, variableValues: variableValues, operationName: nil - ).wait() + ) if let stream = result.stream { return stream diff --git a/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift b/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift index db0ebfb6..e34afe49 100644 --- a/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift +++ b/Tests/GraphQLTests/SubscriptionTests/SubscriptionTests.swift @@ -1,9 +1,7 @@ import GraphQL -import NIO import XCTest /// This follows the graphql-js testing, with deviations where noted. -@available(macOS 10.15, iOS 15, watchOS 8, tvOS 15, *) class SubscriptionTests: XCTestCase { let timeoutDuration = 0.5 // in seconds @@ -30,18 +28,13 @@ class SubscriptionTests: XCTestCase { let subscriptionResult = try await graphqlSubscribe( schema: schema, - request: query, - eventLoopGroup: eventLoopGroup - ).get() - guard let subscription = subscriptionResult.stream else { + request: query + ) + guard let stream = subscriptionResult.stream else { XCTFail(subscriptionResult.errors.description) return } - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -50,7 +43,7 @@ class SubscriptionTests: XCTestCase { unread: true )) db.stop() - let result = try await iterator.next()?.get() + let result = try await iterator.next() XCTAssertEqual( result, GraphQLResult( @@ -85,23 +78,19 @@ class SubscriptionTests: XCTestCase { type: GraphQLInt ), ], - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in + resolve: { emailAny, _, _, _ throws -> Any? in guard let email = emailAny as? Email else { throw GraphQLError( message: "Source is not Email type: \(type(of: emailAny))" ) } - return eventLoopGroup.next().makeSucceededFuture(EmailEvent( + return EmailEvent( email: email, inbox: Inbox(emails: db.emails) - )) + ) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in - eventLoopGroup.next().makeSucceededFuture(db.publisher.subscribe()) + subscribe: { _, _, _, _ throws -> Any? in + db.publisher.subscribe() } ), "notImportantEmail": GraphQLField( @@ -111,29 +100,25 @@ class SubscriptionTests: XCTestCase { type: GraphQLInt ), ], - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in + resolve: { emailAny, _, _, _ throws -> Any? in guard let email = emailAny as? Email else { throw GraphQLError( message: "Source is not Email type: \(type(of: emailAny))" ) } - return eventLoopGroup.next().makeSucceededFuture(EmailEvent( + return EmailEvent( email: email, inbox: Inbox(emails: db.emails) - )) + ) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in - eventLoopGroup.next().makeSucceededFuture(db.publisher.subscribe()) + subscribe: { _, _, _, _ throws -> Any? in + db.publisher.subscribe() } ), ] ) ) - let subscription = try createSubscription(schema: schema, query: """ + let stream = try await createSubscription(schema: schema, query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -147,11 +132,7 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -160,7 +141,7 @@ class SubscriptionTests: XCTestCase { unread: true )) - let result = try await iterator.next()?.get() + let result = try await iterator.next() XCTAssertEqual( result, GraphQLResult( @@ -195,34 +176,28 @@ class SubscriptionTests: XCTestCase { fields: [ "importantEmail": GraphQLField( type: EmailEventType, - resolve: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture(nil) + resolve: { _, _, _, _ throws -> Any? in + nil }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in + subscribe: { _, _, _, _ throws -> Any? in didResolveImportantEmail = true - return eventLoopGroup.next() - .makeSucceededFuture(db.publisher.subscribe()) + return db.publisher.subscribe() } ), "notImportantEmail": GraphQLField( type: EmailEventType, - resolve: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture(nil) + resolve: { _, _, _, _ throws -> Any? in + nil }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture< - Any? - > in + subscribe: { _, _, _, _ throws -> Any? in didResolveNonImportantEmail = true - return eventLoopGroup.next() - .makeSucceededFuture(db.publisher.subscribe()) + return db.publisher.subscribe() } ), ] ) ) - let _ = try createSubscription(schema: schema, query: """ + let _ = try await createSubscription(schema: schema, query: """ subscription { importantEmail { email { @@ -256,15 +231,16 @@ class SubscriptionTests: XCTestCase { // Not implemented because this is taken care of by Swift optional types /// 'resolves to an error for unknown subscription field' - func testErrorUnknownSubscriptionField() throws { + func testErrorUnknownSubscriptionField() async throws { let db = EmailDb() - XCTAssertThrowsError( - try db.subscription(query: """ + do { + _ = try await db.subscription(query: """ subscription { unknownField } """) - ) { error in + XCTFail("Error should have been thrown") + } catch { guard let graphQLError = error as? GraphQLError else { XCTFail("Error was not of type GraphQLError") return @@ -278,25 +254,26 @@ class SubscriptionTests: XCTestCase { } /// 'should pass through unexpected errors thrown in subscribe' - func testPassUnexpectedSubscribeErrors() throws { + func testPassUnexpectedSubscribeErrors() async throws { let db = EmailDb() - XCTAssertThrowsError( - try db.subscription(query: "") - ) + do { + _ = try await db.subscription(query: "") + XCTFail("Error should have been thrown") + } catch {} } /// 'throws an error if subscribe does not return an iterator' - func testErrorIfSubscribeIsntIterator() throws { + func testErrorIfSubscribeIsntIterator() async throws { let schema = try emailSchemaWithResolvers( - resolve: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture(nil) + resolve: { _, _, _, _ throws -> Any? in + nil }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture("test") + subscribe: { _, _, _, _ throws -> Any? in + "test" } ) - XCTAssertThrowsError( - try createSubscription(schema: schema, query: """ + do { + _ = try await createSubscription(schema: schema, query: """ subscription { importantEmail { email { @@ -305,23 +282,24 @@ class SubscriptionTests: XCTestCase { } } """) - ) { error in + XCTFail("Error should have been thrown") + } catch { guard let graphQLError = error as? GraphQLError else { XCTFail("Error was not of type GraphQLError") return } XCTAssertEqual( graphQLError.message, - "Subscription field resolver must return EventStream. Received: 'test'" + "Subscription field resolver must return an AsyncSequence. Received: 'test'" ) } } /// 'resolves to an error for subscription resolver errors' - func testErrorForSubscriptionResolverErrors() throws { - func verifyError(schema: GraphQLSchema) { - XCTAssertThrowsError( - try createSubscription(schema: schema, query: """ + func testErrorForSubscriptionResolverErrors() async throws { + func verifyError(schema: GraphQLSchema) async throws { + do { + _ = try await createSubscription(schema: schema, query: """ subscription { importantEmail { email { @@ -330,7 +308,8 @@ class SubscriptionTests: XCTestCase { } } """) - ) { error in + XCTFail("Error should have been thrown") + } catch { guard let graphQLError = error as? GraphQLError else { XCTFail("Error was not of type GraphQLError") return @@ -340,23 +319,23 @@ class SubscriptionTests: XCTestCase { } // Throwing an error - try verifyError(schema: emailSchemaWithResolvers( - subscribe: { _, _, _, _, _ throws -> EventLoopFuture in + try await verifyError(schema: emailSchemaWithResolvers( + subscribe: { _, _, _, _ throws -> Any? in throw GraphQLError(message: "test error") } )) // Resolving to an error - try verifyError(schema: emailSchemaWithResolvers( - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture(GraphQLError(message: "test error")) + try await verifyError(schema: emailSchemaWithResolvers( + subscribe: { _, _, _, _ throws -> Any? in + GraphQLError(message: "test error") } )) // Rejecting with an error - try verifyError(schema: emailSchemaWithResolvers( - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeFailedFuture(GraphQLError(message: "test error")) + try await verifyError(schema: emailSchemaWithResolvers( + subscribe: { _, _, _, _ throws -> Any? in + GraphQLError(message: "test error") } )) } @@ -365,7 +344,7 @@ class SubscriptionTests: XCTestCase { // Tests above cover this /// 'resolves to an error if variables were wrong type' - func testErrorVariablesWrongType() throws { + func testErrorVariablesWrongType() async throws { let db = EmailDb() let query = """ subscription ($priority: Int) { @@ -382,14 +361,15 @@ class SubscriptionTests: XCTestCase { } """ - XCTAssertThrowsError( - try db.subscription( + do { + _ = try await db.subscription( query: query, variableValues: [ "priority": "meow", ] ) - ) { error in + XCTFail("Should have thrown error") + } catch { guard let graphQLError = error as? GraphQLError else { XCTFail("Error was not of type GraphQLError") return @@ -406,7 +386,7 @@ class SubscriptionTests: XCTestCase { /// 'produces a payload for a single subscriber' func testSingleSubscriber() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -420,11 +400,7 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -434,7 +410,7 @@ class SubscriptionTests: XCTestCase { )) db.stop() - let result = try await iterator.next()?.get() + let result = try await iterator.next() XCTAssertEqual( result, GraphQLResult( @@ -455,7 +431,7 @@ class SubscriptionTests: XCTestCase { /// 'produces a payload for multiple subscribe in same subscription' func testMultipleSubscribers() async throws { let db = EmailDb() - let subscription1 = try db.subscription(query: """ + let stream1 = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -469,12 +445,8 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream1 = subscription1 as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - let subscription2 = try db.subscription(query: """ + let stream2 = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -488,13 +460,9 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream2 = subscription2 as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator1 = stream1.stream.makeAsyncIterator() - var iterator2 = stream2.stream.makeAsyncIterator() + var iterator1 = stream1.makeAsyncIterator() + var iterator2 = stream2.makeAsyncIterator() db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -503,8 +471,8 @@ class SubscriptionTests: XCTestCase { unread: true )) - let result1 = try await iterator1.next()?.get() - let result2 = try await iterator2.next()?.get() + let result1 = try await iterator1.next() + let result2 = try await iterator2.next() let expected = GraphQLResult( data: ["importantEmail": [ @@ -526,7 +494,7 @@ class SubscriptionTests: XCTestCase { /// 'produces a payload per subscription event' func testPayloadPerEvent() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -540,11 +508,7 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() // A new email arrives! db.trigger(email: Email( @@ -553,7 +517,7 @@ class SubscriptionTests: XCTestCase { message: "Tests are good", unread: true )) - let result1 = try await iterator.next()?.get() + let result1 = try await iterator.next() XCTAssertEqual( result1, GraphQLResult( @@ -577,7 +541,7 @@ class SubscriptionTests: XCTestCase { message: "I <3 making things", unread: true )) - let result2 = try await iterator.next()?.get() + let result2 = try await iterator.next() XCTAssertEqual( result2, GraphQLResult( @@ -599,7 +563,7 @@ class SubscriptionTests: XCTestCase { /// This is not in the graphql-js tests. func testArguments() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 5) { importantEmail(priority: $priority) { email { @@ -613,23 +577,8 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - - var results = [GraphQLResult]() - var expectation = XCTestExpectation() - - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - let keepForNow = stream.map { event in - event.map { result in - results.append(result) - expectation.fulfill() - } - } - + var iterator = stream.makeAsyncIterator() + var results = [GraphQLResult?]() var expected = [GraphQLResult]() db.trigger(email: Email( @@ -653,12 +602,10 @@ class SubscriptionTests: XCTestCase { ]] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) // Low priority email shouldn't trigger an event - expectation = XCTestExpectation() - expectation.isInverted = true db.trigger(email: Email( from: "hyo@graphql.org", subject: "Not Important", @@ -666,11 +613,9 @@ class SubscriptionTests: XCTestCase { unread: true, priority: 2 )) - wait(for: [expectation], timeout: timeoutDuration) XCTAssertEqual(results, expected) // Higher priority one should trigger again - expectation = XCTestExpectation() db.trigger(email: Email( from: "hyo@graphql.org", subject: "Tools", @@ -692,18 +637,14 @@ class SubscriptionTests: XCTestCase { ]] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) - - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - _ = keepForNow } /// 'should not trigger when subscription is already done' func testNoTriggerAfterDone() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -717,21 +658,8 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - - var results = [GraphQLResult]() - var expectation = XCTestExpectation() - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - let keepForNow = stream.map { event in - event.map { result in - results.append(result) - expectation.fulfill() - } - } + var iterator = stream.makeAsyncIterator() + var results = [GraphQLResult?]() var expected = [GraphQLResult]() db.trigger(email: Email( @@ -754,28 +682,20 @@ class SubscriptionTests: XCTestCase { ]] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) db.stop() // This should not trigger an event. - expectation = XCTestExpectation() - expectation.isInverted = true db.trigger(email: Email( from: "hyo@graphql.org", subject: "Tools", message: "I <3 making things", unread: true )) - // Ensure that the current result was the one before the db was stopped - wait(for: [expectation], timeout: timeoutDuration) XCTAssertEqual(results, expected) - - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - _ = keepForNow } /// 'should not trigger when subscription is thrown' @@ -784,25 +704,17 @@ class SubscriptionTests: XCTestCase { /// 'event order is correct for multiple publishes' func testOrderCorrectForMultiplePublishes() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { from subject } - inbox { - unread - total - } } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -817,7 +729,9 @@ class SubscriptionTests: XCTestCase { unread: true )) - let result1 = try await iterator.next()?.get() + let result1 = try await iterator.next() + let result2 = try await iterator.next() + XCTAssertEqual( result1, GraphQLResult( @@ -826,15 +740,9 @@ class SubscriptionTests: XCTestCase { "from": "yuzhi@graphql.org", "subject": "Alright", ], - "inbox": [ - "unread": 2, - "total": 3, - ], ]] ) ) - - let result2 = try await iterator.next()?.get() XCTAssertEqual( result2, GraphQLResult( @@ -843,10 +751,6 @@ class SubscriptionTests: XCTestCase { "from": "yuzhi@graphql.org", "subject": "Message 2", ], - "inbox": [ - "unread": 2, - "total": 3, - ], ]] ) ) @@ -857,7 +761,7 @@ class SubscriptionTests: XCTestCase { let db = EmailDb() let schema = try emailSchemaWithResolvers( - resolve: { emailAny, _, _, eventLoopGroup, _ throws -> EventLoopFuture in + resolve: { emailAny, _, _, _ throws -> Any? in guard let email = emailAny as? Email else { throw GraphQLError( message: "Source is not Email type: \(type(of: emailAny))" @@ -866,17 +770,17 @@ class SubscriptionTests: XCTestCase { if email.subject == "Goodbye" { // Force the system to fail here. throw GraphQLError(message: "Never leave.") } - return eventLoopGroup.next().makeSucceededFuture(EmailEvent( + return EmailEvent( email: email, inbox: Inbox(emails: db.emails) - )) + ) }, - subscribe: { _, _, _, eventLoopGroup, _ throws -> EventLoopFuture in - eventLoopGroup.next().makeSucceededFuture(db.publisher.subscribe()) + subscribe: { _, _, _, _ throws -> Any? in + db.publisher.subscribe() } ) - let subscription = try createSubscription(schema: schema, query: """ + let stream = try await createSubscription(schema: schema, query: """ subscription { importantEmail { email { @@ -885,21 +789,8 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - - var results = [GraphQLResult]() - var expectation = XCTestExpectation() - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - let keepForNow = stream.map { event in - event.map { result in - results.append(result) - expectation.fulfill() - } - } + var iterator = stream.makeAsyncIterator() + var results = [GraphQLResult?]() var expected = [GraphQLResult]() db.trigger(email: Email( @@ -917,10 +808,9 @@ class SubscriptionTests: XCTestCase { ]] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) - expectation = XCTestExpectation() // An error in execution is presented as such. db.trigger(email: Email( from: "yuzhi@graphql.org", @@ -936,10 +826,9 @@ class SubscriptionTests: XCTestCase { ] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) - expectation = XCTestExpectation() // However that does not close the response event stream. Subsequent events are still // executed. db.trigger(email: Email( @@ -957,12 +846,8 @@ class SubscriptionTests: XCTestCase { ]] ) ) - wait(for: [expectation], timeout: timeoutDuration) + try await results.append(iterator.next()) XCTAssertEqual(results, expected) - - // So that the Task won't immediately be cancelled since the ConcurrentEventStream is - // discarded - _ = keepForNow } /// 'should pass through error thrown in source event stream' @@ -971,7 +856,7 @@ class SubscriptionTests: XCTestCase { /// Test incorrect emitted type errors func testErrorWrongEmitType() async throws { let db = EmailDb() - let subscription = try db.subscription(query: """ + let stream = try await db.subscription(query: """ subscription ($priority: Int = 0) { importantEmail(priority: $priority) { email { @@ -985,15 +870,11 @@ class SubscriptionTests: XCTestCase { } } """) - guard let stream = subscription as? ConcurrentEventStream else { - XCTFail("stream isn't ConcurrentEventStream") - return - } - var iterator = stream.stream.makeAsyncIterator() + var iterator = stream.makeAsyncIterator() db.publisher.emit(event: "String instead of email") - let result = try await iterator.next()?.get() + let result = try await iterator.next() XCTAssertEqual( result, GraphQLResult( diff --git a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift index 915331bd..e6ed44fb 100644 --- a/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift +++ b/Tests/GraphQLTests/TypeTests/GraphQLSchemaTests.swift @@ -47,7 +47,7 @@ class GraphQLSchemaTests: XCTestCase { ), ], interfaces: [interface], - isTypeOf: { _, _, _ -> Bool in + isTypeOf: { _, _ -> Bool in preconditionFailure("Should not be called") } ) @@ -81,7 +81,7 @@ class GraphQLSchemaTests: XCTestCase { ), ], interfaces: [interface], - isTypeOf: { _, _, _ -> Bool in + isTypeOf: { _, _ -> Bool in preconditionFailure("Should not be called") } ) @@ -110,7 +110,7 @@ class GraphQLSchemaTests: XCTestCase { ), ], interfaces: [interface], - isTypeOf: { _, _, _ -> Bool in + isTypeOf: { _, _ -> Bool in preconditionFailure("Should not be called") } ) diff --git a/Tests/GraphQLTests/TypeTests/IntrospectionTests.swift b/Tests/GraphQLTests/TypeTests/IntrospectionTests.swift index 0a990391..c85c5bf8 100644 --- a/Tests/GraphQLTests/TypeTests/IntrospectionTests.swift +++ b/Tests/GraphQLTests/TypeTests/IntrospectionTests.swift @@ -1,19 +1,8 @@ @testable import GraphQL -import NIO import XCTest class IntrospectionTests: XCTestCase { - private var eventLoopGroup: EventLoopGroup! - - override func setUp() { - eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - } - - override func tearDown() { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - - func testDefaultValues() throws { + func testDefaultValues() async throws { let numEnum = try GraphQLEnumType( name: "Enum", values: [ @@ -110,7 +99,7 @@ class IntrospectionTests: XCTestCase { let schema = try GraphQLSchema(query: query, types: [inputObject, outputObject]) - let introspection = try graphql( + let introspection = try await graphql( schema: schema, request: """ query IntrospectionTypeQuery { @@ -133,9 +122,8 @@ class IntrospectionTests: XCTestCase { } } } - """, - eventLoopGroup: eventLoopGroup - ).wait() + """ + ) let queryType = try XCTUnwrap( introspection.data?["__schema"]["types"].array? diff --git a/Tests/GraphQLTests/TypeTests/ScalarTests.swift b/Tests/GraphQLTests/TypeTests/ScalarTests.swift index 59bc6e87..494c6b93 100644 --- a/Tests/GraphQLTests/TypeTests/ScalarTests.swift +++ b/Tests/GraphQLTests/TypeTests/ScalarTests.swift @@ -1,5 +1,4 @@ @testable import GraphQL -import NIO import XCTest class ScalarTests: XCTestCase { diff --git a/Tests/GraphQLTests/UtilitiesTests/BuildASTSchemaTests.swift b/Tests/GraphQLTests/UtilitiesTests/BuildASTSchemaTests.swift index b7a34a44..91429598 100644 --- a/Tests/GraphQLTests/UtilitiesTests/BuildASTSchemaTests.swift +++ b/Tests/GraphQLTests/UtilitiesTests/BuildASTSchemaTests.swift @@ -1,5 +1,4 @@ @testable import GraphQL -import NIO import XCTest class BuildASTSchemaTests: XCTestCase { @@ -12,7 +11,7 @@ class BuildASTSchemaTests: XCTestCase { return try printSchema(schema: buildSchema(source: sdl)) } - func testCanUseBuiltSchemaForLimitedExecution() throws { + func testCanUseBuiltSchemaForLimitedExecution() async throws { let schema = try buildASTSchema( documentAST: parse( source: """ @@ -23,12 +22,11 @@ class BuildASTSchemaTests: XCTestCase { ) ) - let result = try graphql( + let result = try await graphql( schema: schema, request: "{ str }", - rootValue: ["str": 123], - eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1) - ).wait() + rootValue: ["str": 123] + ) XCTAssertEqual( result, @@ -50,16 +48,15 @@ class BuildASTSchemaTests: XCTestCase { // ) // ) // -// let result = try graphql( +// let result = try await graphql( // schema: schema, // request: "{ add(x: 34, y: 55) }", // rootValue: [ // "add": { (x: Int, y: Int) in // return x + y // } -// ], -// eventLoopGroup: MultiThreadedEventLoopGroup(numberOfThreads: 1) -// ).wait() +// ] +// ) // // XCTAssertEqual( // result, diff --git a/Tests/GraphQLTests/UtilitiesTests/ExtendSchemaTests.swift b/Tests/GraphQLTests/UtilitiesTests/ExtendSchemaTests.swift index 6c3a9625..fae4c70c 100644 --- a/Tests/GraphQLTests/UtilitiesTests/ExtendSchemaTests.swift +++ b/Tests/GraphQLTests/UtilitiesTests/ExtendSchemaTests.swift @@ -1,18 +1,7 @@ @testable import GraphQL -import NIO import XCTest class ExtendSchemaTests: XCTestCase { - private var eventLoopGroup: EventLoopGroup! - - override func setUp() { - eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) - } - - override func tearDown() { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } - func schemaChanges( _ schema: GraphQLSchema, _ extendedSchema: GraphQLSchema @@ -46,7 +35,7 @@ class ExtendSchemaTests: XCTestCase { ) } - func testCanBeUsedForLimitedExecution() throws { + func testCanBeUsedForLimitedExecution() async throws { let schema = try buildSchema(source: "type Query") let extendAST = try parse(source: """ extend type Query { @@ -54,12 +43,11 @@ class ExtendSchemaTests: XCTestCase { } """) let extendedSchema = try extendSchema(schema: schema, documentAST: extendAST) - let result = try graphql( + let result = try await graphql( schema: extendedSchema, request: "{ newField }", - rootValue: ["newField": 123], - eventLoopGroup: eventLoopGroup - ).wait() + rootValue: ["newField": 123] + ) XCTAssertEqual( result, .init(data: ["newField": "123"]) diff --git a/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift b/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift index 2350c285..d0571f94 100644 --- a/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift +++ b/Tests/GraphQLTests/ValidationTests/ExampleSchema.swift @@ -15,7 +15,7 @@ let ValidationExampleBeing = try! GraphQLInterfaceType( } ), ], - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -32,7 +32,7 @@ let ValidationExampleMammal = try! GraphQLInterfaceType( "father": GraphQLField(type: ValidationExampleMammal), ] }, - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -53,7 +53,7 @@ let ValidationExamplePet = try! GraphQLInterfaceType( } ), ], - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -78,7 +78,7 @@ let ValidationExampleCanine = try! GraphQLInterfaceType( type: ValidationExampleMammal ), ], - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -263,7 +263,7 @@ let ValidationExampleCat = try! GraphQLObjectType( // union CatOrDog = Cat | Dog let ValidationExampleCatOrDog = try! GraphQLUnionType( name: "CatOrDog", - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" }, types: [ValidationExampleCat, ValidationExampleDog] @@ -277,7 +277,7 @@ let ValidationExampleIntelligent = try! GraphQLInterfaceType( fields: [ "iq": GraphQLField(type: GraphQLInt), ], - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -293,7 +293,7 @@ let ValidationExampleSentient = try! GraphQLInterfaceType( return nil }, ], - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" } ) @@ -363,7 +363,7 @@ let ValidationExampleCatCommand = try! GraphQLEnumType( // union DogOrHuman = Dog | Human let ValidationExampleDogOrHuman = try! GraphQLUnionType( name: "DogOrHuman", - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" }, types: [ValidationExampleDog, ValidationExampleHuman] @@ -372,7 +372,7 @@ let ValidationExampleDogOrHuman = try! GraphQLUnionType( // union HumanOrAlien = Human | Alien let ValidationExampleHumanOrAlien = try! GraphQLUnionType( name: "HumanOrAlien", - resolveType: { _, _, _ in + resolveType: { _, _ in "Unknown" }, types: [ValidationExampleHuman, ValidationExampleAlien]