diff --git a/Package.swift b/Package.swift index 624cf6b5..94353929 100644 --- a/Package.swift +++ b/Package.swift @@ -8,8 +8,10 @@ let package = Package( .macOS(.v10_13), ], products: [ - // core library + // this library exports `AWSLambdaRuntimeCore` and adds Foundation convenience methods .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), + // this has all the main functionality for lambda and it does not link Foundation + .library(name: "AWSLambdaRuntimeCore", targets: ["AWSLambdaRuntimeCore"]), // common AWS events .library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]), // for testing only @@ -22,23 +24,40 @@ let package = Package( ], targets: [ .target(name: "AWSLambdaRuntime", dependencies: [ + .byName(name: "AWSLambdaRuntimeCore"), + .product(name: "NIO", package: "swift-nio"), + .product(name: "NIOFoundationCompat", package: "swift-nio"), + ]), + .target(name: "AWSLambdaRuntimeCore", dependencies: [ .product(name: "Logging", package: "swift-log"), .product(name: "Backtrace", package: "swift-backtrace"), .product(name: "NIOHTTP1", package: "swift-nio"), - .product(name: "NIOFoundationCompat", package: "swift-nio"), ]), - .testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]), + .testTarget(name: "AWSLambdaRuntimeCoreTests", dependencies: [ + .byName(name: "AWSLambdaRuntimeCore"), + ]), + .testTarget(name: "AWSLambdaRuntimeTests", dependencies: [ + .byName(name: "AWSLambdaRuntimeCore"), + .byName(name: "AWSLambdaRuntime"), + ]), .target(name: "AWSLambdaEvents", dependencies: []), .testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]), // testing helper .target(name: "AWSLambdaTesting", dependencies: [ - "AWSLambdaRuntime", + .byName(name: "AWSLambdaRuntime"), .product(name: "NIO", package: "swift-nio"), ]), - .testTarget(name: "AWSLambdaTestingTests", dependencies: ["AWSLambdaTesting"]), + .testTarget(name: "AWSLambdaTestingTests", dependencies: [ + .byName(name: "AWSLambdaTesting"), + .byName(name: "AWSLambdaRuntime"), + ]), // samples - .target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]), - .target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]), + .target(name: "StringSample", dependencies: [ + .byName(name: "AWSLambdaRuntime"), + ]), + .target(name: "CodableSample", dependencies: [ + .byName(name: "AWSLambdaRuntime"), + ]), // perf tests .target(name: "MockServer", dependencies: [ .product(name: "NIOHTTP1", package: "swift-nio"), diff --git a/Sources/AWSLambdaRuntime/Context+Foundation.swift b/Sources/AWSLambdaRuntime/Context+Foundation.swift new file mode 100644 index 00000000..0aa1c019 --- /dev/null +++ b/Sources/AWSLambdaRuntime/Context+Foundation.swift @@ -0,0 +1,23 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftAWSLambdaRuntime open source project +// +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import AWSLambdaRuntimeCore +import struct Foundation.Date + +extension Lambda.Context { + var deadlineDate: Date { + let secondsSinceEpoch = Double(Int64(bitPattern: self.deadline.rawValue)) / -1_000_000_000 + return Date(timeIntervalSince1970: secondsSinceEpoch) + } +} diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index e24e736d..23743a98 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAWSLambdaRuntime open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +@_exported import AWSLambdaRuntimeCore import class Foundation.JSONDecoder import class Foundation.JSONEncoder import NIO @@ -29,7 +30,7 @@ extension Lambda { /// /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. public static func run(_ closure: @escaping CodableClosure) { - self.run(closure: closure) + self.run(CodableClosureWrapper(closure)) } /// An asynchronous Lambda Closure that takes a `In: Decodable` and returns a `Result` via a completion handler. @@ -42,19 +43,7 @@ extension Lambda { /// /// - note: This is a blocking operation that will run forever, as its lifecycle is managed by the AWS Lambda Runtime Engine. public static func run(_ closure: @escaping CodableVoidClosure) { - self.run(closure: closure) - } - - // for testing - @discardableResult - internal static func run(configuration: Configuration = .init(), closure: @escaping CodableClosure) -> Result { - self.run(configuration: configuration, handler: CodableClosureWrapper(closure)) - } - - // for testing - @discardableResult - internal static func run(configuration: Configuration = .init(), closure: @escaping CodableVoidClosure) -> Result { - self.run(configuration: configuration, handler: CodableVoidClosureWrapper(closure)) + self.run(CodableVoidClosureWrapper(closure)) } } diff --git a/Sources/AWSLambdaRuntime/HTTPClient.swift b/Sources/AWSLambdaRuntimeCore/HTTPClient.swift similarity index 100% rename from Sources/AWSLambdaRuntime/HTTPClient.swift rename to Sources/AWSLambdaRuntimeCore/HTTPClient.swift diff --git a/Sources/AWSLambdaRuntime/Lambda+String.swift b/Sources/AWSLambdaRuntimeCore/Lambda+String.swift similarity index 100% rename from Sources/AWSLambdaRuntime/Lambda+String.swift rename to Sources/AWSLambdaRuntimeCore/Lambda+String.swift diff --git a/Sources/AWSLambdaRuntime/Lambda.swift b/Sources/AWSLambdaRuntimeCore/Lambda.swift similarity index 100% rename from Sources/AWSLambdaRuntime/Lambda.swift rename to Sources/AWSLambdaRuntimeCore/Lambda.swift diff --git a/Sources/AWSLambdaRuntime/LambdaConfiguration.swift b/Sources/AWSLambdaRuntimeCore/LambdaConfiguration.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaConfiguration.swift rename to Sources/AWSLambdaRuntimeCore/LambdaConfiguration.swift diff --git a/Sources/AWSLambdaRuntime/LambdaContext.swift b/Sources/AWSLambdaRuntimeCore/LambdaContext.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaContext.swift rename to Sources/AWSLambdaRuntimeCore/LambdaContext.swift diff --git a/Sources/AWSLambdaRuntime/LambdaHandler.swift b/Sources/AWSLambdaRuntimeCore/LambdaHandler.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaHandler.swift rename to Sources/AWSLambdaRuntimeCore/LambdaHandler.swift diff --git a/Sources/AWSLambdaRuntime/LambdaLifecycle.swift b/Sources/AWSLambdaRuntimeCore/LambdaLifecycle.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaLifecycle.swift rename to Sources/AWSLambdaRuntimeCore/LambdaLifecycle.swift diff --git a/Sources/AWSLambdaRuntime/LambdaRunner.swift b/Sources/AWSLambdaRuntimeCore/LambdaRunner.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaRunner.swift rename to Sources/AWSLambdaRuntimeCore/LambdaRunner.swift diff --git a/Sources/AWSLambdaRuntime/LambdaRuntimeClient.swift b/Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift similarity index 100% rename from Sources/AWSLambdaRuntime/LambdaRuntimeClient.swift rename to Sources/AWSLambdaRuntimeCore/LambdaRuntimeClient.swift diff --git a/Sources/AWSLambdaRuntime/Utils.swift b/Sources/AWSLambdaRuntimeCore/Utils.swift similarity index 100% rename from Sources/AWSLambdaRuntime/Utils.swift rename to Sources/AWSLambdaRuntimeCore/Utils.swift diff --git a/Sources/AWSLambdaTesting/Lambda+Testing.swift b/Sources/AWSLambdaTesting/Lambda+Testing.swift index d8b2b125..da14faa1 100644 --- a/Sources/AWSLambdaTesting/Lambda+Testing.swift +++ b/Sources/AWSLambdaTesting/Lambda+Testing.swift @@ -17,6 +17,7 @@ // @testable is used to access of internal functions #if DEBUG @testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Dispatch import Logging import NIO diff --git a/Sources/CodableSample/main.swift b/Sources/CodableSample/main.swift index eb985f41..34e845b1 100644 --- a/Sources/CodableSample/main.swift +++ b/Sources/CodableSample/main.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftAWSLambdaRuntime open source project // -// Copyright (c) 2017-2018 Apple Inc. and the SwiftAWSLambdaRuntime project authors +// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information diff --git a/Sources/StringSample/main.swift b/Sources/StringSample/main.swift index fddfa478..c7d0434a 100644 --- a/Sources/StringSample/main.swift +++ b/Sources/StringSample/main.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -import AWSLambdaRuntime +import AWSLambdaRuntimeCore import NIO // in this example we are receiving and responding with strings diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+StringTest.swift b/Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift similarity index 99% rename from Tests/AWSLambdaRuntimeTests/Lambda+StringTest.swift rename to Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift index 9eebf2d2..a0be16d6 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+StringTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Lambda+StringTest.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import NIO import XCTest diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRunnerTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRunnerTest.swift similarity index 98% rename from Tests/AWSLambdaRuntimeTests/LambdaRunnerTest.swift rename to Tests/AWSLambdaRuntimeCoreTests/LambdaRunnerTest.swift index 71707681..f91bde4a 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRunnerTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRunnerTest.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import XCTest class LambdaRunnerTest: XCTestCase { diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift similarity index 99% rename from Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTest.swift rename to Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift index f0b7c17d..ea3e279d 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaRuntimeClientTest.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import XCTest class LambdaRuntimeClientTest: XCTestCase { diff --git a/Tests/AWSLambdaRuntimeTests/LambdaTest.swift b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift similarity index 99% rename from Tests/AWSLambdaRuntimeTests/LambdaTest.swift rename to Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift index 6ff75fc1..60cc2e9a 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaTest.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/LambdaTest.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Logging import NIO import XCTest diff --git a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift similarity index 99% rename from Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift rename to Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift index 595ee011..b2f671a6 100644 --- a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/MockLambdaServer.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Foundation // for JSON import Logging import NIO diff --git a/Tests/AWSLambdaRuntimeTests/Utils.swift b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift similarity index 98% rename from Tests/AWSLambdaRuntimeTests/Utils.swift rename to Tests/AWSLambdaRuntimeCoreTests/Utils.swift index 8cf46b6b..11e6004e 100644 --- a/Tests/AWSLambdaRuntimeTests/Utils.swift +++ b/Tests/AWSLambdaRuntimeCoreTests/Utils.swift @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -@testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore import Logging import NIO import XCTest diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift index 1d4b79dc..371783b5 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodeableTest.swift @@ -13,266 +13,77 @@ //===----------------------------------------------------------------------===// @testable import AWSLambdaRuntime +@testable import AWSLambdaRuntimeCore +import Logging import NIO +import NIOFoundationCompat import XCTest class CodableLambdaTest: XCTestCase { - func testCallbackSuccess() { - let server = MockLambdaServer(behavior: Behavior()) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } + var eventLoopGroup: EventLoopGroup! + let allocator = ByteBufferAllocator() - struct Handler: LambdaHandler { - typealias In = Request - typealias Out = Response - - func handle(context: Lambda.Context, payload: Request, callback: (Result) -> Void) { - callback(.success(Response(requestId: payload.requestId))) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testVoidCallbackSuccess() { - let server = MockLambdaServer(behavior: Behavior(result: .success(nil))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: LambdaHandler { - typealias In = Request - typealias Out = Void - - func handle(context: Lambda.Context, payload: Request, callback: (Result) -> Void) { - callback(.success(())) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testCallbackFailure() { - let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: LambdaHandler { - typealias In = Request - typealias Out = Response - - func handle(context: Lambda.Context, payload: Request, callback: (Result) -> Void) { - callback(.failure(TestError("boom"))) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testEventLoopSuccess() { - let server = MockLambdaServer(behavior: Behavior()) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: EventLoopLambdaHandler { - typealias In = Request - typealias Out = Response - - func handle(context: Lambda.Context, payload: Request) -> EventLoopFuture { - context.eventLoop.makeSucceededFuture(Response(requestId: payload.requestId)) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testVoidEventLoopSuccess() { - let server = MockLambdaServer(behavior: Behavior(result: .success(nil))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: EventLoopLambdaHandler { - typealias In = Request - typealias Out = Void - - func handle(context: Lambda.Context, payload: Request) -> EventLoopFuture { - context.eventLoop.makeSucceededFuture(()) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testEventLoopFailure() { - let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: EventLoopLambdaHandler { - typealias In = Request - typealias Out = Response - - func handle(context: Lambda.Context, payload: Request) -> EventLoopFuture { - context.eventLoop.makeFailedFuture(TestError("boom")) - } - } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration, handler: Handler()) - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testClosureSuccess() { - let server = MockLambdaServer(behavior: Behavior()) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration) { (_, payload: Request, callback) in - callback(.success(Response(requestId: payload.requestId))) - } - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) + override func setUp() { + self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) } - func testVoidClosureSuccess() { - let server = MockLambdaServer(behavior: Behavior(result: .success(nil))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result = Lambda.run(configuration: configuration) { (_, _: Request, callback: (Result) -> Void) in - callback(.success(())) - } - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) - } - - func testClosureFailure() { - let server = MockLambdaServer(behavior: Behavior(result: .failure(TestError("boom")))) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - let maxTimes = Int.random(in: 1 ... 10) - let configuration = Lambda.Configuration(lifecycle: .init(maxTimes: maxTimes)) - let result: Result = Lambda.run(configuration: configuration) { (_, _: Request, callback: (Result) -> Void) in - callback(.failure(TestError("boom"))) - } - assertLambdaLifecycleResult(result, shoudHaveRun: maxTimes) + override func tearDown() { + try! self.eventLoopGroup.syncShutdownGracefully() } - func testBootstrapFailure() { - let server = MockLambdaServer(behavior: FailedBootstrapBehavior()) - XCTAssertNoThrow(try server.start().wait()) - defer { XCTAssertNoThrow(try server.stop().wait()) } - - struct Handler: LambdaHandler { - typealias In = Request - typealias Out = Response + func testCodableVoidClosureWrapper() { + let request = Request(requestId: UUID().uuidString) + var inputBuffer: ByteBuffer? + var outputBuffer: ByteBuffer? - init(eventLoop: EventLoop) throws { - throw TestError("kaboom") - } - - func handle(context: Lambda.Context, payload: Request, callback: (Result) -> Void) { - callback(.failure(TestError("should not be called"))) - } + let closureWrapper = CodableVoidClosureWrapper { (_, _: Request, completion) in + XCTAssertEqual(request, request) + completion(.success(())) } - let result = Lambda.run(factory: Handler.init) - assertLambdaLifecycleResult(result, shouldFailWithError: TestError("kaboom")) - } -} - -// TODO: taking advantage of the fact we know the serialization is json -private struct Behavior: LambdaServerBehavior { - let requestId: String - let payload: String - let result: Result - - init(requestId: String = UUID().uuidString, payload: String = "hello", result: Result = .success("hello")) { - self.requestId = requestId - self.payload = payload - self.result = result + XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) + XCTAssertNoThrow(outputBuffer = try closureWrapper.handle(context: self.newContext(), payload: XCTUnwrap(inputBuffer)).wait()) + XCTAssertNil(outputBuffer) } - func getInvocation() -> GetInvocationResult { - guard let payload = try? JSONEncoder().encode(Request(requestId: requestId)) else { - XCTFail("encoding error") - return .failure(.internalServerError) - } - guard let payloadAsString = String(data: payload, encoding: .utf8) else { - XCTFail("encoding error") - return .failure(.internalServerError) - } - return .success((requestId: self.requestId, payload: payloadAsString)) - } + func testCodableClosureWrapper() { + let request = Request(requestId: UUID().uuidString) + var inputBuffer: ByteBuffer? + var outputBuffer: ByteBuffer? + var response: Response? - func processResponse(requestId: String, response: String?) -> Result { - switch self.result { - case .success(let expected) where expected != nil: - guard let data = response?.data(using: .utf8) else { - XCTFail("decoding error") - return .failure(.internalServerError) - } - guard let response = try? JSONDecoder().decode(Response.self, from: data) else { - XCTFail("decoding error") - return .failure(.internalServerError) - } - XCTAssertEqual(self.requestId, response.requestId, "expecting requestId to match") - return .success(()) - case .success(let expected) where expected == nil: - XCTAssertNil(response) - return .success(()) - case .failure: - XCTFail("unexpected to fail, but succeeded with: \(response ?? "undefined")") - return .failure(.internalServerError) - default: - preconditionFailure("invalid state") + let closureWrapper = CodableClosureWrapper { (_, req: Request, completion: (Result) -> Void) in + XCTAssertEqual(request, request) + completion(.success(Response(requestId: req.requestId))) } - } - func processError(requestId: String, error: ErrorResponse) -> Result { - XCTAssertEqual(self.requestId, requestId, "expecting requestId to match") - switch self.result { - case .success: - XCTFail("unexpected to succeed, but failed with: \(error)") - return .failure(.internalServerError) - case .failure(let expected): - XCTAssertEqual(expected.description, error.errorMessage, "expecting error to match") - return .success(()) - } + XCTAssertNoThrow(inputBuffer = try JSONEncoder().encode(request, using: self.allocator)) + XCTAssertNoThrow(outputBuffer = try closureWrapper.handle(context: self.newContext(), payload: XCTUnwrap(inputBuffer)).wait()) + XCTAssertNoThrow(response = try JSONDecoder().decode(Response.self, from: XCTUnwrap(outputBuffer))) + XCTAssertEqual(response?.requestId, request.requestId) } - func processInitError(error: ErrorResponse) -> Result { - XCTFail("should not report init error") - return .failure(.internalServerError) + // convencience method + func newContext() -> Lambda.Context { + Lambda.Context(requestId: UUID().uuidString, + traceId: "abc123", + invokedFunctionArn: "aws:arn:", + deadline: .now() + .seconds(3), + cognitoIdentity: nil, + clientContext: nil, + logger: Logger(label: "test"), + eventLoop: self.eventLoopGroup.next()) } } -private struct Request: Codable { +private struct Request: Codable, Equatable { let requestId: String init(requestId: String) { self.requestId = requestId } } -private struct Response: Codable { +private struct Response: Codable, Equatable { let requestId: String init(requestId: String) { self.requestId = requestId