From adfb2dbfc9635b9d7803b490ef2c8734b4226b9b Mon Sep 17 00:00:00 2001 From: Paulo Faria Date: Fri, 26 Jun 2020 20:09:24 -0300 Subject: [PATCH] Add scalars and simple async resolve functions. --- README.md | 2 +- Sources/Graphiti/ComponentsInitializer.swift | 58 ++++++++++++++++++ Sources/Graphiti/FieldsInitializer.swift | 35 +++++++++++ Sources/Graphiti/SimpleAsyncResolve.swift | 8 +++ .../HelloWorldTests/HelloWorldTests.swift | 2 +- .../StarWarsIntrospectionTests.swift | 59 ++----------------- .../StarWarsTests/StarWarsQueryTests.swift | 2 +- 7 files changed, 109 insertions(+), 57 deletions(-) create mode 100644 Sources/Graphiti/SimpleAsyncResolve.swift diff --git a/README.md b/README.md index d73e6fbf..abb3397b 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ import NIO let root = MessageAPI() let context = MessageStore() let service = try MessageService(root: root) -let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) +let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) defer { try? group.syncShutdownGracefully() diff --git a/Sources/Graphiti/ComponentsInitializer.swift b/Sources/Graphiti/ComponentsInitializer.swift index 412a70bf..fa9deba5 100644 --- a/Sources/Graphiti/ComponentsInitializer.swift +++ b/Sources/Graphiti/ComponentsInitializer.swift @@ -1,3 +1,6 @@ +import Foundation +import GraphQL + public final class ComponentsInitializer { var components: [Component] = [] @@ -168,4 +171,59 @@ public final class ComponentsInitializer { components.append(component) return ComponentInitializer(component) } + + @discardableResult + public func dateScalar( + formatter: Formatter + ) -> ComponentInitializer { + scalar( + Date.self, + serialize: { date in + guard let string = formatter.string(for: date) else { + throw GraphQLError(message: "Invalid value for Date scalar. Cannot convert \"\(date)\" to string.") + } + + return .string(string) + }, + parse: { map in + guard let string = map.string else { + throw GraphQLError(message: "Invalid type for Date scalar. Expected string, but got \(map.typeDescription)") + } + + var date = Date() + let datePointer = AutoreleasingUnsafeMutablePointer(&date) + + guard formatter.getObjectValue(datePointer, for: string, errorDescription: nil) else { + throw GraphQLError(message: "Invalid date string for Date scalar.") + } + + guard let formattedDate = datePointer.pointee as? Date else { + throw GraphQLError(message: "Invalid date string for Date scalar.") + } + + return formattedDate + } + ) + } + + @discardableResult + public func urlScalar() -> ComponentInitializer { + scalar( + URL.self, + serialize: { url in + .string(url.absoluteString) + }, + parse: { map in + guard let string = map.string else { + throw GraphQLError(message: "Invalid type for URL scalar.") + } + + guard let url = URL(string: string) else { + throw GraphQLError(message: "Invalid url string for URL scalar.") + } + + return url + } + ) + } } diff --git a/Sources/Graphiti/FieldsInitializer.swift b/Sources/Graphiti/FieldsInitializer.swift index c7828589..ff18f3b8 100644 --- a/Sources/Graphiti/FieldsInitializer.swift +++ b/Sources/Graphiti/FieldsInitializer.swift @@ -1,3 +1,5 @@ +import NIO + public final class FieldsInitializer where Keys.RawValue == String { var fields: [FieldComponent] = [] @@ -96,4 +98,37 @@ public final class FieldsInitializer( + _ name: Keys, + at function: @escaping SimpleAsyncResolve, + overridingType: FieldType.Type = FieldType.self + ) -> FieldInitializer { + let asyncResolve: AsyncResolve = { type in + return { context, arguments, group in + // We hop to guarantee that the future will + // return in the same event loop group of the execution. + try function(type)(context, arguments).hop(to: group.next()) + } + } + + return field(name, at: asyncResolve, overridingType: overridingType) + } + + @discardableResult + public func field( + _ name: Keys, + at function: @escaping SimpleAsyncResolve + ) -> FieldInitializer { + let asyncResolve: AsyncResolve = { type in + return { context, arguments, group in + // We hop to guarantee that the future will + // return in the same event loop group of the execution. + try function(type)(context, arguments).hop(to: group.next()) + } + } + + return field(name, at: asyncResolve) + } } diff --git a/Sources/Graphiti/SimpleAsyncResolve.swift b/Sources/Graphiti/SimpleAsyncResolve.swift new file mode 100644 index 00000000..989ad72a --- /dev/null +++ b/Sources/Graphiti/SimpleAsyncResolve.swift @@ -0,0 +1,8 @@ +import NIO + +public typealias SimpleAsyncResolve = ( + _ object: ObjectType +) -> ( + _ context: Context, + _ arguments: Arguments +) throws -> EventLoopFuture diff --git a/Tests/GraphitiTests/HelloWorldTests/HelloWorldTests.swift b/Tests/GraphitiTests/HelloWorldTests/HelloWorldTests.swift index e0360230..df4f5c54 100644 --- a/Tests/GraphitiTests/HelloWorldTests/HelloWorldTests.swift +++ b/Tests/GraphitiTests/HelloWorldTests/HelloWorldTests.swift @@ -146,7 +146,7 @@ struct HelloService : Service { class HelloWorldTests : XCTestCase { private let service = HelloService() - private var group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private var group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) deinit { try? self.group.syncShutdownGracefully() diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift index 494cfdeb..a0522549 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsIntrospectionTests.swift @@ -6,14 +6,13 @@ import GraphQL class StarWarsIntrospectionTests : XCTestCase { private let service = StarWarsService() + private let group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) + + deinit { + try? group.syncShutdownGracefully() + } func testIntrospectionTypeQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionTypeQuery {" + " __schema {" + " types {" + @@ -95,12 +94,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionQueryTypeQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionQueryTypeQuery {" + " __schema {" + " queryType {" + @@ -129,12 +122,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionDroidTypeQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionDroidTypeQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -159,12 +146,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionDroidKindQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionDroidKindQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -191,12 +172,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionCharacterKindQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionCharacterKindQuery {" + " __type(name: \"Character\") {" + " name" + @@ -223,12 +198,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionDroidFieldsQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionDroidFieldsQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -304,12 +273,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionDroidNestedFieldsQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionDroidNestedFieldsQuery {" + " __type(name: \"Droid\") {" + " name" + @@ -410,12 +373,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionFieldArgsQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionFieldArgsQuery {" + " __schema {" + " queryType {" + @@ -529,12 +486,6 @@ class StarWarsIntrospectionTests : XCTestCase { } func testIntrospectionDroidDescriptionQuery() throws { - let group = MultiThreadedEventLoopGroup(numberOfThreads: 1) - - defer { - XCTAssertNoThrow(try group.syncShutdownGracefully()) - } - let query = "query IntrospectionDroidDescriptionQuery {" + " __type(name: \"Droid\") {" + " name" + diff --git a/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift b/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift index 90cbe111..f020dc2d 100644 --- a/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift +++ b/Tests/GraphitiTests/StarWarsTests/StarWarsQueryTests.swift @@ -6,7 +6,7 @@ import GraphQL @available(OSX 10.15, *) class StarWarsQueryTests : XCTestCase { private let service = StarWarsService() - private var group = MultiThreadedEventLoopGroup(numberOfThreads: 1) + private var group = MultiThreadedEventLoopGroup(numberOfThreads: System.coreCount) deinit { try? self.group.syncShutdownGracefully()