diff --git a/.gitignore b/.gitignore index 02c0875..83497f9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .DS_Store /.build /Packages +Package.resolved /*.xcodeproj diff --git a/.travis.yml b/.travis.yml index d95aa63..2673632 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ os: language: generic sudo: required dist: trusty -osx_image: xcode9.3 +osx_image: xcode10 env: - - SWIFT_VERSION=4.1 + - SWIFT_VERSION=4.2 install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update && brew bundle; fi - eval "$(curl -sL https://swiftenv.fuller.li/install.sh)" diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..03e15b6 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,187 @@ +{ + "object": { + "pins": [ + { + "package": "Console", + "repositoryURL": "https://github.com/vapor/console.git", + "state": { + "branch": null, + "revision": "5b9796d39f201b3dd06800437abd9d774a455e57", + "version": "3.0.2" + } + }, + { + "package": "Core", + "repositoryURL": "https://github.com/vapor/core.git", + "state": { + "branch": null, + "revision": "eb876a758733166a4fb20f3f0a17b480c5ea563e", + "version": "3.4.3" + } + }, + { + "package": "Crypto", + "repositoryURL": "https://github.com/vapor/crypto.git", + "state": { + "branch": null, + "revision": "4b85405430df1892ee3aa1554bdb477e96cf46ad", + "version": "3.2.0" + } + }, + { + "package": "DatabaseKit", + "repositoryURL": "https://github.com/vapor/database-kit.git", + "state": { + "branch": null, + "revision": "3a17dbbe9be5f8c37703e4b7982c1332ad6b00c4", + "version": "1.3.1" + } + }, + { + "package": "Graphiti", + "repositoryURL": "https://github.com/noahemmet/Graphiti.git", + "state": { + "branch": "swift-42", + "revision": "693e2a0f3b0967bf13b3812f01e5614aabbb7201", + "version": null + } + }, + { + "package": "GraphQL", + "repositoryURL": "https://github.com/noahemmet/GraphQL.git", + "state": { + "branch": "swift-42", + "revision": "f6da781ffc6e2db343f928bf204cf90dce25e540", + "version": null + } + }, + { + "package": "HTTP", + "repositoryURL": "https://github.com/vapor/http.git", + "state": { + "branch": null, + "revision": "9e3eff9dfa4df7fc282bf27f801c72b3ffbfd984", + "version": "3.1.4" + } + }, + { + "package": "Multipart", + "repositoryURL": "https://github.com/vapor/multipart.git", + "state": { + "branch": null, + "revision": "e57007c23a52b68e44ebdfc70cbe882a7c4f1ec3", + "version": "3.0.2" + } + }, + { + "package": "Routing", + "repositoryURL": "https://github.com/vapor/routing.git", + "state": { + "branch": null, + "revision": "3219e328491b0853b8554c5a694add344d2c6cfb", + "version": "3.0.1" + } + }, + { + "package": "Runtime", + "repositoryURL": "https://github.com/wickwirew/Runtime.git", + "state": { + "branch": "swift42", + "revision": "a34cd8f9a1e76e0d0c32f47abae49d6134830825", + "version": null + } + }, + { + "package": "Service", + "repositoryURL": "https://github.com/vapor/service.git", + "state": { + "branch": null, + "revision": "281a70b69783891900be31a9e70051b6fe19e146", + "version": "1.0.0" + } + }, + { + "package": "swift-nio", + "repositoryURL": "https://github.com/apple/swift-nio.git", + "state": { + "branch": null, + "revision": "5d8148c8b45dfb449276557f22120694567dd1d2", + "version": "1.9.5" + } + }, + { + "package": "swift-nio-ssl", + "repositoryURL": "https://github.com/apple/swift-nio-ssl.git", + "state": { + "branch": null, + "revision": "8380fa29a2af784b067d8ee01c956626ca29f172", + "version": "1.3.1" + } + }, + { + "package": "swift-nio-ssl-support", + "repositoryURL": "https://github.com/apple/swift-nio-ssl-support.git", + "state": { + "branch": null, + "revision": "c02eec4e0e6d351cd092938cf44195a8e669f555", + "version": "1.0.0" + } + }, + { + "package": "swift-nio-zlib-support", + "repositoryURL": "https://github.com/apple/swift-nio-zlib-support.git", + "state": { + "branch": null, + "revision": "37760e9a52030bb9011972c5213c3350fa9d41fd", + "version": "1.0.0" + } + }, + { + "package": "TemplateKit", + "repositoryURL": "https://github.com/vapor/template-kit.git", + "state": { + "branch": null, + "revision": "db35b1c35aabd0f5db3abca0cfda7becfe9c43e2", + "version": "1.1.0" + } + }, + { + "package": "URLEncodedForm", + "repositoryURL": "https://github.com/vapor/url-encoded-form.git", + "state": { + "branch": null, + "revision": "932024f363ee5ff59059cf7d67194a1c271d3d0c", + "version": "1.0.5" + } + }, + { + "package": "Validation", + "repositoryURL": "https://github.com/vapor/validation.git", + "state": { + "branch": null, + "revision": "156f8adeac3440e868da3757777884efbc6ec0cc", + "version": "2.1.0" + } + }, + { + "package": "Vapor", + "repositoryURL": "https://github.com/vapor/vapor.git", + "state": { + "branch": null, + "revision": "157d3b15336caa882662cc75024dd04b2e225246", + "version": "3.1.0" + } + }, + { + "package": "WebSocket", + "repositoryURL": "https://github.com/vapor/websocket.git", + "state": { + "branch": null, + "revision": "149af03348f60ac8b84defdf277112d62fd8c704", + "version": "1.0.2" + } + } + ] + }, + "version": 1 +} diff --git a/Package.swift b/Package.swift index a5de03b..fe958eb 100644 --- a/Package.swift +++ b/Package.swift @@ -9,17 +9,30 @@ let package = Package( .library( name: "VaporGraphQL", targets: ["VaporGraphQL"]), + .library( + name: "StarWars", + targets: ["StarWars"]), + .executable( + name: "Example", + targets: ["Example"]) ], dependencies: [ - .package(url: "https://github.com/vapor/vapor.git", .upToNextMajor(from: "3.0.0")), - .package(url: "https://github.com/GraphQLSwift/GraphQL.git", .upToNextMajor(from: "0.5.0")) + .package(url: "https://github.com/vapor/vapor.git", .exact("3.1.0")), + .package(url: "https://github.com/noahemmet/GraphQL.git", .branch("spm")), + .package(url: "https://github.com/noahemmet/Graphiti.git", .branch("master")), ], targets: [ .target( name: "VaporGraphQL", - dependencies: ["Vapor", "GraphQL"]), + dependencies: ["Vapor", "GraphQL", "Graphiti"]), + .target( + name: "StarWars", + dependencies: ["Vapor", "GraphQL", "Graphiti"]), + .target( + name: "Example", + dependencies: ["VaporGraphQL", "StarWars", "Vapor", "GraphQL", "Graphiti"]), .testTarget( name: "VaporGraphQLTests", - dependencies: ["VaporGraphQL"]), + dependencies: ["VaporGraphQL", "StarWars"]), ] ) diff --git a/README.md b/README.md index 38dbe5d..48eb622 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ import PackageDescription let package = Package( dependencies: [ ... - .package(url: "https://github.com/stevenlambion/vapor-graphql.git", .upToNextMajor(from: "0.1.0")), + .package(url: "https://github.com/stevenlambion/GraphQLRouteCollection.git", .upToNextMajor(from: "0.1.0")), ], .target( name: "App", @@ -36,21 +36,17 @@ services.register(HTTPGraphQL() { req in ( schema: schema, rootValue: [:], context: req -)}); +)}) ``` -Then use it in your app's routing: +Then route it in your app's `config.swift` file: ```swift -let router = app.make(Router.self) -let graphql = app.make(GraphQLService.self) - -router.get("/graphql") { req in - return graphql.execute(req) -} -router.post("/graphql") { req in - return graphql.execute(req) -} +let router = EngineRouter.default() +let graphQLRouteCollection = GraphQLRouteCollection(enableGraphiQL: true) +try graphQLRouteCollection.boot(router: router) +try routes(router) +services.register(router, as: Router.self) ``` ### Introspection @@ -100,46 +96,6 @@ You can also enable it for the route collection: GraphQLRouteCollection(enableGraphiQL: true) ``` -### Type Safety - -The Swift GraphQL library does not provide a fully type safe environment currently. To mitigate this, there's a `withTypedResolve()` decorator function to wrap resolvers that provide typed sources and context objects. This function has multiple overloads for both field and type resolvers. The function helps to provide better type support in development and runtime checking of the GraphQL API. - -```swift -let UserType = try! GraphQLObjectType( - name: "User", - fields: [ - "id": GraphQLField(type: GraphQLNonNull(GraphQLID)), - "email": GraphQLField(type: GraphQLNonNull(GraphQLString)), - "role": GraphQLField( - type: UserRoleType, - resolve: withTypedResolve { (user: User, _,) -> Role in - try user.role.get().wait() - }) - ] -) -``` - -### Async Support - -Vapor 3 has a strong focus on async, however, the Swift GraphQL library hasn't implemented this functionality yet. To mitigate this problem the HTTPGraphQL service runs executions using GCD so as not to block the NIO event loop. It also uses the concurrency strategy for queries and subscriptions for parallel field resolving. - -For async resolvers, a `resolveWithFuture()` decorator is provided. This allows resolvers to work with async code based on Swift NIO as they typically would in Vapor. `resolveWithFuture()` works by waiting for the results under an async queue separate from the NIO event loop. `resolveWithFuture()` is composed using the `withTypedResolve()` decorator, so it also includes type handling. - -```swift -let UserType = try! GraphQLObjectType( - name: "User", - fields: [ - "id": GraphQLField(type: GraphQLNonNull(GraphQLID)), - "email": GraphQLField(type: GraphQLNonNull(GraphQLString)), - "role": GraphQLField( - type: UserRoleType, - resolve: resolveWithFuture { (user: User, _,) -> Future in - try user.role.get() - }) - ] -) -``` - ## License This project is released under the MIT license. See [LICENSE](LICENSE) for details. diff --git a/Sources/Example/app.swift b/Sources/Example/app.swift new file mode 100644 index 0000000..6135d1e --- /dev/null +++ b/Sources/Example/app.swift @@ -0,0 +1,11 @@ +import Vapor + +/// Creates an instance of Application. This is called from main.swift in the run target. +public func app(_ env: Environment) throws -> Application { + var config = Config.default() + var env = env + var services = Services.default() + try configure(&config, &env, &services) + let app = try Application(config: config, environment: env, services: services) + return app +} diff --git a/Sources/Example/configure.swift b/Sources/Example/configure.swift new file mode 100644 index 0000000..d47401c --- /dev/null +++ b/Sources/Example/configure.swift @@ -0,0 +1,26 @@ +import Vapor +import VaporGraphQL +import StarWars + +/// Called before your application initializes. +public func configure(_ config: inout Config, _ env: inout Vapor.Environment, _ services: inout Services) throws { + let httpGraphQL = HTTPGraphQL() { req in + return ExecutionContext( + schema: starWarsSchema, + eventLoopGroup: req + ) + } + services.register(httpGraphQL, as: GraphQLService.self) + + /// Register routes to the router + let router = EngineRouter.default() + let graphQLRouteCollection = GraphQLRouteCollection(enableGraphiQL: true) + try graphQLRouteCollection.boot(router: router) + try routes(router) + services.register(router, as: Router.self) + + /// Register middleware + var middlewares = MiddlewareConfig() // Create _empty_ middleware config + middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response + services.register(middlewares) +} diff --git a/Sources/Example/main.swift b/Sources/Example/main.swift new file mode 100644 index 0000000..0379894 --- /dev/null +++ b/Sources/Example/main.swift @@ -0,0 +1,6 @@ +import Vapor +import VaporGraphQL +import GraphQL + +public let application = try app(.detect()) +try application.run() diff --git a/Sources/Example/routes.swift b/Sources/Example/routes.swift new file mode 100644 index 0000000..166735c --- /dev/null +++ b/Sources/Example/routes.swift @@ -0,0 +1,7 @@ +import Vapor + +/// Register your application's routes here. +public func routes(_ router: Router) throws { + // GraphQL and GraphiQL are handled by the RouteCollection in `configure.swift`, + // but additional endpoints can still be added here. +} diff --git a/Tests/VaporGraphQLTests/StarWarsData.swift b/Sources/StarWars/StarWarsData.swift similarity index 100% rename from Tests/VaporGraphQLTests/StarWarsData.swift rename to Sources/StarWars/StarWarsData.swift diff --git a/Tests/VaporGraphQLTests/StarWarsSchema.swift b/Sources/StarWars/StarWarsSchema.swift similarity index 80% rename from Tests/VaporGraphQLTests/StarWarsSchema.swift rename to Sources/StarWars/StarWarsSchema.swift index 1b651ad..6767ed8 100644 --- a/Tests/VaporGraphQLTests/StarWarsSchema.swift +++ b/Sources/StarWars/StarWarsSchema.swift @@ -1,6 +1,5 @@ import GraphQL import Vapor -import VaporGraphQL /** * Taken from: @@ -74,7 +73,7 @@ extension Droid : MapFallibleRepresentable {} * This implements the following type system shorthand: * enum Episode { NEWHOPE, EMPIRE, JEDI } */ -let EpisodeEnum = try! GraphQLEnumType( +let episodeEnum = try! GraphQLEnumType( name: "Episode", description: "One of the films in the Star Wars Trilogy", values: [ @@ -105,7 +104,7 @@ let EpisodeEnum = try! GraphQLEnumType( * secretBackstory: String * } */ -let CharacterInterface = try! GraphQLInterfaceType( +let characterInterface = try! GraphQLInterfaceType( name: "Character", description: "A character in the Star Wars Trilogy", fields: [ @@ -122,7 +121,7 @@ let CharacterInterface = try! GraphQLInterfaceType( description: "The friends of the character, or an empty list if they have none." ), "appearsIn": GraphQLField( - type: GraphQLList(EpisodeEnum), + type: GraphQLList(episodeEnum), description: "Which movies they appear in." ), "secretBackstory": GraphQLField( @@ -153,7 +152,7 @@ let CharacterInterface = try! GraphQLInterfaceType( * secretBackstory: String * } */ -let HumanType = try! GraphQLObjectType( +let humanType = try! GraphQLObjectType( name: "Human", description: "A humanoid creature in the Star Wars universe.", fields: [ @@ -166,15 +165,16 @@ let HumanType = try! GraphQLObjectType( description: "The name of the human." ), "friends": GraphQLField( - type: GraphQLList(CharacterInterface), + type: GraphQLList(characterInterface), description: "The friends of the human, or an empty list if they " + "have none.", - resolve: { human, _, _, _ in - return getFriends(character: human as! Human) + resolve: { human, _, _, eventLoopGroup, _ in + let friends = getFriends(character: human as! Human) + return eventLoopGroup.next().newSucceededFuture(result: friends) } ), "appearsIn": GraphQLField( - type: GraphQLList(EpisodeEnum), + type: GraphQLList(episodeEnum), description: "Which movies they appear in." ), "homePlanet": GraphQLField( @@ -184,7 +184,7 @@ let HumanType = try! GraphQLObjectType( "secretBackstory": GraphQLField( type: GraphQLString, description: "Where are they from and how they came to be who they are.", - resolve: { _, _, _, _ in + resolve: { _, _, _, _, _ in struct Secret : Error, CustomStringConvertible { let description: String } @@ -193,7 +193,7 @@ let HumanType = try! GraphQLObjectType( } ), ], - interfaces: [CharacterInterface], + interfaces: [characterInterface], isTypeOf: { source, _, _ in source is Human } @@ -213,7 +213,7 @@ let HumanType = try! GraphQLObjectType( * primaryFunction: String * } */ -let DroidType = try! GraphQLObjectType( +let droidType = try! GraphQLObjectType( name: "Droid", description: "A mechanical creature in the Star Wars universe.", fields: [ @@ -226,20 +226,21 @@ let DroidType = try! GraphQLObjectType( description: "The name of the droid." ), "friends": GraphQLField( - type: GraphQLList(CharacterInterface), + type: GraphQLList(characterInterface), description: "The friends of the droid, or an empty list if they have none.", - resolve: { droid, _, _, _ in - return getFriends(character: droid as! Droid) + resolve: { droid, _, _, eventLoopGroup, _ in + let friends = getFriends(character: droid as! Droid) + return eventLoopGroup.next().newSucceededFuture(result: friends) } ), "appearsIn": GraphQLField( - type: GraphQLList(EpisodeEnum), + type: GraphQLList(episodeEnum), description: "Which movies they appear in." ), "secretBackstory": GraphQLField( type: GraphQLString, description: "Construction date and the name of the designer.", - resolve: { _, _, _, _ in + resolve: { _, _, _, _, _ in struct Secret : Error, CustomStringConvertible { let description: String } @@ -252,7 +253,7 @@ let DroidType = try! GraphQLObjectType( description: "The primary function of the droid." ), ], - interfaces: [CharacterInterface], + interfaces: [characterInterface], isTypeOf: { source, _, _ in source is Droid } @@ -273,49 +274,51 @@ let DroidType = try! GraphQLObjectType( * } * */ -let QueryType = try! GraphQLObjectType( +let queryType = try! GraphQLObjectType( name: "Query", fields: [ + "hero": GraphQLField( - type: CharacterInterface, + type: characterInterface, args: [ "episode": GraphQLArgument( - type: EpisodeEnum, + type: episodeEnum, description: "If omitted, returns the hero of the whole saga. If " + "provided, returns the hero of that particular episode." ) ], - resolve: resolveWithFuture { (source: Any, arguments: Map, context: Request) -> Future in - return context.eventLoop.submit { + resolve: { source, arguments, _, eventLoopGroup, _ in let episode = Episode(arguments["episode"].string) - return getHero(episode: episode) - } + let hero = getHero(episode: episode) + return eventLoopGroup.next().newSucceededFuture(result: hero) } ), "human": GraphQLField( - type: HumanType, + type: humanType, args: [ "id": GraphQLArgument( type: GraphQLNonNull(GraphQLString), description: "id of the human" ) ], - resolve: resolveWithFuture { (source: Any, arguments: Map, context: Request) -> Future in - return context.eventLoop.submit { getHuman(id: arguments["id"].string!) } + resolve: { source, arguments, _, eventLoopGroup, _ in + let human = getHuman(id: arguments["id"].string!) + return eventLoopGroup.next().newSucceededFuture(result: human) } ), "droid": GraphQLField( - type: DroidType, + type: droidType, args: [ "id": GraphQLArgument( type: GraphQLNonNull(GraphQLString), description: "id of the droid" ) ], - resolve: resolveWithFuture { (source: Any, arguments: Map, context: Request) -> Future in - return context.eventLoop.submit { getDroid(id: arguments["id"].string!) } - } + resolve: { source, arguments, _, eventLoopGroup, _ in + let droid = getDroid(id: arguments["id"].string!) + return eventLoopGroup.next().newSucceededFuture(result: droid) + } ), ] ) @@ -324,7 +327,7 @@ let QueryType = try! GraphQLObjectType( * Finally, we construct our schema (whose starting query type is the query * type we defined above) and export it. */ -let StarWarsSchema = try! GraphQLSchema( - query: QueryType, - types: [HumanType, DroidType] +public let starWarsSchema = try! GraphQLSchema( + query: queryType, + types: [humanType, droidType] ) diff --git a/Sources/VaporGraphQL/GraphQLExecutionRequest.swift b/Sources/VaporGraphQL/GraphQLExecutionRequest.swift index 8c5850d..41f94e2 100644 --- a/Sources/VaporGraphQL/GraphQLExecutionRequest.swift +++ b/Sources/VaporGraphQL/GraphQLExecutionRequest.swift @@ -8,6 +8,14 @@ public struct GraphQLExecutionRequest: Content { let variables: [String:Map] let operationName: String? + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + query = try container.decode(String.self, forKey: .query) + // the decoder has trouble decoding a nil `[String: Map]`, so we'll use an optional throw. + variables = (try? container.decode([String: Map].self, forKey: .variables)) ?? [:] + operationName = try? container.decode(String.self, forKey: .operationName) + } + init(query: String, variables: [String:Map]?, operationName: String?) { self.query = query self.variables = variables ?? [:] diff --git a/Sources/VaporGraphQL/GraphQLService+Introspection.swift b/Sources/VaporGraphQL/GraphQLService+Introspection.swift index 9ca1d68..0fed3b5 100644 --- a/Sources/VaporGraphQL/GraphQLService+Introspection.swift +++ b/Sources/VaporGraphQL/GraphQLService+Introspection.swift @@ -94,7 +94,7 @@ extension GraphQLService { /// Returns an introspection of the GraphQL schema. public func executeIntrospectionQuery(for req: Request) -> Future { return self.execute( - GraphQLExecutionRequest(query: introspectionQuery, variables: [:], operationName: ""), + GraphQLExecutionRequest(query: introspectionQuery, variables: [:], operationName: nil), for: req ) } diff --git a/Sources/VaporGraphQL/HTTPGraphQL.swift b/Sources/VaporGraphQL/HTTPGraphQL.swift index 2d9cbf6..0bd52f9 100644 --- a/Sources/VaporGraphQL/HTTPGraphQL.swift +++ b/Sources/VaporGraphQL/HTTPGraphQL.swift @@ -1,11 +1,20 @@ import Vapor import GraphQL +import Graphiti -public typealias ExecutionContext = ( - schema: GraphQLSchema, - rootValue: Any, - context: Any -) +public struct ExecutionContext { + public let schema: GraphQLSchema + public let rootValue: Any + public let context: Any + public let eventLoopGroup: EventLoopGroup + + public init(schema: GraphQLSchema, rootValue: Any = (), context: Any = (), eventLoopGroup: EventLoopGroup) { + self.schema = schema + self.rootValue = rootValue + self.context = context + self.eventLoopGroup = eventLoopGroup + } +} /// Provides HTTPGraphQL an execution context to perform per request. public typealias ExecutionContextProvider = (Request) throws -> ExecutionContext @@ -19,52 +28,40 @@ public struct HTTPGraphQL: GraphQLService { /// given an empty query. public let enableIntrospection: Bool - /// Moves the graphql execution to a separate thread so the NIO event loop - /// isn't blocked. - private let dispatchQueue: DispatchQueue - public init( enableIntrospection: Bool = true, executionContextProvider: @escaping ExecutionContextProvider) { self.executionContextProvider = executionContextProvider self.enableIntrospection = enableIntrospection - self.dispatchQueue = DispatchQueue( - label: "HTTP GraphQL Execution Queue", - qos: .userInitiated, - attributes: .concurrent - ) } public func execute(_ executionRequest: GraphQLExecutionRequest, for req: Request) -> Future { if executionRequest.query == "" && executionRequest.variables.isEmpty && self.enableIntrospection { return self.executeIntrospectionQuery(for: req) } - let promise = req.eventLoop.newPromise(Map.self); - self.dispatchQueue.async { do { - let (schema, rootValue, context) = try self.executionContextProvider(req) - promise.succeed(result: try graphql( - queryStrategy: ConcurrentDispatchFieldExecutionStrategy(dispatchQueue: self.dispatchQueue), - subscriptionStrategy: ConcurrentDispatchFieldExecutionStrategy(dispatchQueue: self.dispatchQueue), - schema: schema, - request: executionRequest.query, - rootValue: rootValue, - contextValue: context, - variableValues: executionRequest.variables, - operationName: executionRequest.operationName) - ) + let executionContext = try self.executionContextProvider(req) + let result = try graphql( + schema: executionContext.schema, + request: executionRequest.query, + rootValue: executionContext.rootValue, + context: executionContext.context, + eventLoopGroup: executionContext.eventLoopGroup, + variableValues: executionRequest.variables, + operationName: executionRequest.operationName) + return result } catch let e as GraphQLError { - promise.succeed(result: [ - "data": [ - "errors": [e.map] - ] - ]) + let graphQLError: [String: Map] = [ + "data": [ + "errors": [e.map] + ] + ] + let map = Map.dictionary(graphQLError) + return req.future(map) } catch let e { - promise.fail(error: e) + return req.future(error: e) } - } - return promise.futureResult } } diff --git a/Sources/VaporGraphQL/resolveWithFuture.swift b/Sources/VaporGraphQL/resolveWithFuture.swift deleted file mode 100644 index a871169..0000000 --- a/Sources/VaporGraphQL/resolveWithFuture.swift +++ /dev/null @@ -1,28 +0,0 @@ -/// Wrap resolver functions that return a promise, so they work correctly with -/// the GraphQL executor. They assume the initial execution happens within a future -/// using the concurrent strategy. - -import GraphQL -import Vapor - -public func resolveWithFuture(resolver: @escaping TypedFieldResolver1>) throws -> GraphQLFieldResolve { - return withTypedResolve { (source: Source, args: Map) -> Result? in - try resolver(source, args)?.wait() - } -} - -public func resolveWithFuture( - resolver: @escaping TypedFieldResolver2> -) throws -> GraphQLFieldResolve { - return withTypedResolve { (source: Source, args: Map, context: Context) -> Result? in - try resolver(source, args, context)?.wait() - } -} - -public func resolveWithFuture( - resolver: @escaping TypedFieldResolver3> -) throws -> GraphQLFieldResolve { - return withTypedResolve { (source: Source, args: Map, context: Context, info: GraphQLResolveInfo) -> Result? in - try resolver(source, args, context, info)?.wait() - } -} diff --git a/Sources/VaporGraphQL/withTypedResolve.swift b/Sources/VaporGraphQL/withTypedResolve.swift deleted file mode 100644 index 02cc067..0000000 --- a/Sources/VaporGraphQL/withTypedResolve.swift +++ /dev/null @@ -1,108 +0,0 @@ -/// Wraps resolvers to provide better type support when working with GraphQL. - -import GraphQL - -enum TypedFieldResolveError: Error { - case InvalidType -} - -public typealias TypedTypeResolver1 = ( - _ value: Value - ) throws -> Result - -public typealias TypedTypeResolver2 = ( - _ value: Value, - _ context: Context - ) throws -> Result - -public typealias TypedTypeResolver3 = ( - _ value: Value, - _ context: Context, - _ info: GraphQLResolveInfo - ) throws -> Result - -public typealias TypedFieldResolver1 = ( - _ source: Source, - _ args: Map - ) throws -> Result? - -public typealias TypedFieldResolver2 = ( - _ source: Source, - _ args: Map, - _ context: Context - ) throws -> Result? - -public typealias TypedFieldResolver3 = ( - _ source: Source, - _ args: Map, - _ context: Context, - _ info: GraphQLResolveInfo - ) throws -> Result? - -public func withTypedResolve(resolver: @escaping TypedFieldResolver1) -> GraphQLFieldResolve { - return { (source: Any, args: Map, context: Any, info: GraphQLResolveInfo) throws in - guard let typedSource = source as? Source else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedSource, args) - } -} - -public func withTypedResolve(resolver: @escaping TypedFieldResolver2) -> GraphQLFieldResolve { - return { (source: Any, args: Map, context: Any, info: GraphQLResolveInfo) throws in - guard - let typedSource = source as? Source, - let typedContext = context as? Context else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedSource, args, typedContext) - } -} - -public func withTypedResolve(resolver: @escaping TypedFieldResolver3) -> GraphQLFieldResolve { - return { (source: Any, args: Map, context: Any, info: GraphQLResolveInfo) throws in - guard - let typedSource = source as? Source, - let typedContext = context as? Context else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedSource, args, typedContext, info) - } -} - -public func withTypedResolve( - resolver: @escaping TypedTypeResolver1 -) throws -> GraphQLTypeResolve { - return { (value: Any, context: Any, info: GraphQLResolveInfo) throws in - guard let typedValue = value as? Value else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedValue) - } -} - -public func withTypedResolve( - resolver: @escaping TypedTypeResolver2 -) throws -> GraphQLTypeResolve { - return { (value: Any, context: Any, info: GraphQLResolveInfo) throws in - guard - let typedValue = value as? Value, - let typedContext = context as? Context else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedValue, typedContext) - } -} - -public func withTypedResolve( - resolver: @escaping TypedTypeResolver3 - ) throws -> GraphQLTypeResolve { - return { (value: Any, context: Any, info: GraphQLResolveInfo) throws in - guard - let typedValue = value as? Value, - let typedContext = context as? Context else { - throw TypedFieldResolveError.InvalidType - } - return try resolver(typedValue, typedContext, info) - } -} diff --git a/Tests/VaporGraphQLTests/HTTPGraphQLTests.swift b/Tests/VaporGraphQLTests/HTTPGraphQLTests.swift index f947a5b..1d8893b 100644 --- a/Tests/VaporGraphQLTests/HTTPGraphQLTests.swift +++ b/Tests/VaporGraphQLTests/HTTPGraphQLTests.swift @@ -1,5 +1,6 @@ import XCTest @testable import VaporGraphQL +@testable import StarWars import Vapor import HTTP import GraphQL @@ -10,10 +11,9 @@ func urlEncode(_ stringToEncode: String) -> String { func createHTTPGraphQL() -> HTTPGraphQL { return HTTPGraphQL() { req in - ( - schema: StarWarsSchema, - rootValue: [:], - context: req + return ExecutionContext( + schema: starWarsSchema, + eventLoopGroup: req ) } }