Skip to content
This repository was archived by the owner on Mar 19, 2024. It is now read-only.

Commit 597686e

Browse files
Add more lambda handlers using the NIO coding style
1 parent 4357d56 commit 597686e

File tree

8 files changed

+881
-6
lines changed

8 files changed

+881
-6
lines changed

Sources/LambdaSwiftSprinterNioPlugin/LambdaApiNIO.swift

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public var httpClient: HTTPClientProtocol = {
3939
}()
4040

4141
public protocol HTTPClientProtocol: class {
42+
var eventLoopGroup: EventLoopGroup { get }
4243
func get(url: String, deadline: NIODeadline?) -> EventLoopFuture<HTTPClient.Response>
4344
func post(url: String, body: HTTPClient.Body?, deadline: NIODeadline?) -> EventLoopFuture<HTTPClient.Response>
4445
func execute(request: HTTPClient.Request, deadline: NIODeadline?) -> EventLoopFuture<HTTPClient.Response>
@@ -54,19 +55,28 @@ extension HTTPClient: HTTPClientProtocol {
5455
public class LambdaApiNIO: LambdaAPI {
5556

5657
let urlBuilder: LambdaRuntimeAPIUrlBuilder
58+
59+
private let _nextInvocationRequest: HTTPClient.Request
5760

5861
/// Construct a `LambdaApiNIO` class.
5962
///
6063
/// - parameters
6164
/// - awsLambdaRuntimeAPI: AWS_LAMBDA_RUNTIME_API
6265
public required init(awsLambdaRuntimeAPI: String) throws {
6366
self.urlBuilder = try LambdaRuntimeAPIUrlBuilder(awsLambdaRuntimeAPI: awsLambdaRuntimeAPI)
67+
self._nextInvocationRequest = try HTTPClient.Request(url: urlBuilder.nextInvocationURL(), method: .GET)
6468
}
6569

70+
/// Call the next invocation API to get the next event. The response body contains the event data. Response headers contain the `RequestID` and other information.
71+
///
72+
/// - returns:
73+
/// - `(event: Data, responseHeaders: [AnyHashable: Any])` the event to process and the responseHeaders
74+
/// - throws:
75+
/// - `invalidBuffer` if the body is empty or the buffer doesn't contain data.
76+
/// - `invalidResponse(HTTPResponseStatus)` if the HTTP response is not valid.
6677
public func getNextInvocation() throws -> (event: Data, responseHeaders: [AnyHashable: Any]) {
67-
let request = try HTTPClient.Request(url: urlBuilder.nextInvocationURL(), method: .GET)
6878
let result = try httpClient.execute(
69-
request: request,
79+
request: _nextInvocationRequest,
7080
deadline: nil
7181
).wait()
7282

@@ -86,6 +96,13 @@ public class LambdaApiNIO: LambdaAPI {
8696
}
8797
}
8898

99+
/// Sends an invocation response to Lambda.
100+
///
101+
/// - parameters:
102+
/// - requestId: Request ID
103+
/// - httpBody: data body.
104+
/// - throws:
105+
/// - HttpClient errors
89106
public func postInvocationResponse(for requestId: String, httpBody: Data) throws {
90107
var request = try HTTPClient.Request(
91108
url: urlBuilder.invocationResponseURL(requestId: requestId),
@@ -98,6 +115,13 @@ public class LambdaApiNIO: LambdaAPI {
98115
).wait()
99116
}
100117

118+
/// Sends an invocation error to Lambda.
119+
///
120+
/// - parameters:
121+
/// - requestId: Request ID
122+
/// - error: error
123+
/// - throws:
124+
/// - HttpClient errors
101125
public func postInvocationError(for requestId: String, error: Error) throws {
102126
let errorMessage = String(describing: error)
103127
let invocationError = InvocationError(errorMessage: errorMessage,
@@ -114,6 +138,12 @@ public class LambdaApiNIO: LambdaAPI {
114138
).wait()
115139
}
116140

141+
/// Sends an initialization error to Lambda.
142+
///
143+
/// - parameters:
144+
/// - error: error
145+
/// - throws:
146+
/// - HttpClient errors
117147
public func postInitializationError(error: Error) throws {
118148
let errorMessage = String(describing: error)
119149
let invocationError = InvocationError(errorMessage: errorMessage,
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
// Copyright 2019 (c) Andrea Scuderi - https://github.com/swift-sprinter
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import Foundation
16+
import NIO
17+
import NIOHTTP1
18+
import NIOFoundationCompat
19+
import LambdaSwiftSprinter
20+
21+
public typealias AsyncDictionaryNIOLambda = ([String: Any], Context, @escaping (DictionaryResult) -> Void) -> Void
22+
public typealias AsyncCodableNIOLambda<Event: Decodable, Response: Encodable> = (Event, Context, @escaping (Result<Response, Error>) -> Void) -> Void
23+
24+
public typealias SyncDictionaryNIOLambda = ([String: Any], Context) throws -> [String: Any]
25+
public typealias SyncCodableNIOLambda<Event: Decodable, Response: Encodable> = (Event, Context) -> EventLoopFuture<Response>
26+
27+
public protocol SyncNIOLambdaHandler: LambdaHandler {
28+
29+
func handler(event: Data, context: Context) -> EventLoopFuture<Data>
30+
}
31+
32+
public extension SyncNIOLambdaHandler {
33+
34+
func commonHandler(event: Data, context: Context) -> LambdaResult {
35+
do {
36+
let data = try handler(event: event, context: context).wait()
37+
return .success(data)
38+
} catch {
39+
return .failure(error)
40+
}
41+
}
42+
}
43+
44+
public protocol AsyncNIOLambdaHandler: LambdaHandler {
45+
46+
func handler(event: Data, context: Context, completion: @escaping (LambdaResult) -> Void)
47+
}
48+
49+
public extension AsyncNIOLambdaHandler {
50+
func commonHandler(event: Data, context: Context) -> LambdaResult {
51+
let eventLoop = httpClient.eventLoopGroup.next()
52+
let promise = eventLoop.makePromise(of: LambdaResult.self)
53+
handler(event: event, context: context) { result in
54+
55+
switch result {
56+
case .success(_):
57+
promise.succeed(result)
58+
case .failure(let error):
59+
promise.fail(error)
60+
}
61+
}
62+
do {
63+
let result = try promise.futureResult.wait()
64+
return result
65+
} catch {
66+
return .failure(error)
67+
}
68+
}
69+
}
70+
71+
struct CodableSyncNIOLambdaHandler<Event: Decodable, Response: Encodable>: SyncNIOLambdaHandler {
72+
73+
let handlerFunction: (Event, Context) -> EventLoopFuture<Response>
74+
75+
func handler(event: Data, context: Context) -> EventLoopFuture<Data> {
76+
77+
let eventLoop = httpClient.eventLoopGroup.next()
78+
let promise = eventLoop.makePromise(of: Data.self)
79+
do {
80+
let eventObj = try event.decode() as Event
81+
let responseObj = try handlerFunction(eventObj, context).wait()
82+
let response = try Data(from: responseObj)
83+
promise.succeed(response)
84+
} catch {
85+
promise.fail(error)
86+
}
87+
return promise.futureResult
88+
}
89+
}
90+
91+
struct CodableAsyncNIOLambdaHandler<Event: Decodable, Response: Encodable>: AsyncNIOLambdaHandler {
92+
let handlerFunction: AsyncCodableLambda<Event, Response>
93+
94+
func handler(event: Data, context: Context, completion: @escaping (LambdaResult) -> Void) {
95+
do {
96+
let data = try event.decode() as Event
97+
handlerFunction(data, context) { outputResult in
98+
switch outputResult {
99+
case .failure(let error):
100+
completion(.failure(error))
101+
case .success(let outputDict):
102+
do {
103+
let outputData = try Data(from: outputDict)
104+
completion(.success(outputData))
105+
} catch {
106+
completion(.failure(error))
107+
}
108+
}
109+
}
110+
} catch {
111+
completion(.failure(error))
112+
}
113+
}
114+
}
115+
116+
struct DictionarySyncNIOLambdaHandler: SyncNIOLambdaHandler {
117+
118+
let completionHandler: ([String: Any], Context) throws -> [String: Any]
119+
func handler(event: Data, context: Context) -> EventLoopFuture<Data> {
120+
121+
let eventLoop = httpClient.eventLoopGroup.next()
122+
let promise = eventLoop.makePromise(of: Data.self)
123+
do {
124+
let data = try event.jsonObject()
125+
let output = try completionHandler(data, context)
126+
let responseObj = try Data(jsonObject: output)
127+
promise.succeed(responseObj)
128+
} catch {
129+
promise.fail(error)
130+
}
131+
return promise.futureResult
132+
}
133+
}
134+
135+
struct DictionaryAsyncNIOLambdaHandler: AsyncNIOLambdaHandler {
136+
let completionHandler: AsyncDictionaryLambda
137+
138+
func handler(event: Data, context: Context, completion: @escaping (LambdaResult) -> Void) {
139+
do {
140+
let jsonDictionary = try event.jsonObject()
141+
completionHandler(jsonDictionary, context) { outputResult in
142+
switch outputResult {
143+
case .failure(let error):
144+
completion(.failure(error))
145+
case .success(let outputDict):
146+
do {
147+
let outputData = try Data(jsonObject: outputDict)
148+
completion(.success(outputData))
149+
} catch {
150+
completion(.failure(error))
151+
}
152+
}
153+
}
154+
} catch {
155+
completion(.failure(error))
156+
}
157+
}
158+
}
159+
160+
public extension Sprinter where API == LambdaApiNIO {
161+
func register(handler name: String, lambda: @escaping SyncDictionaryNIOLambda) {
162+
let handler = DictionarySyncNIOLambdaHandler(completionHandler: lambda)
163+
register(handler: name, lambda: handler)
164+
}
165+
166+
func register(handler name: String, lambda: @escaping AsyncDictionaryNIOLambda) {
167+
let handler = DictionaryAsyncNIOLambdaHandler(completionHandler: lambda)
168+
register(handler: name, lambda: handler)
169+
}
170+
171+
func register<Event: Decodable, Response: Encodable>(handler name: String,
172+
lambda: @escaping SyncCodableNIOLambda<Event, Response>) {
173+
let handler = CodableSyncNIOLambdaHandler(handlerFunction: lambda)
174+
register(handler: name, lambda: handler)
175+
}
176+
177+
func register<Event: Decodable, Response: Encodable>(handler name: String,
178+
lambda: @escaping AsyncCodableNIOLambda<Event, Response>) {
179+
let handler = CodableAsyncNIOLambdaHandler(handlerFunction: lambda)
180+
register(handler: name, lambda: handler)
181+
}
182+
}

Sources/LambdaSwiftSprinterNioPlugin/Utils.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Foundation
1616
import NIO
1717
import NIOHTTP1
1818
import NIOFoundationCompat
19+
import LambdaSwiftSprinter
1920

2021
internal extension HTTPHeaders {
2122
var dictionary: [String: String] {
@@ -33,6 +34,24 @@ internal extension Data {
3334
let jsonEncoder = JSONEncoder()
3435
self = try jsonEncoder.encode(object)
3536
}
37+
38+
func decode<T: Decodable>() throws -> T {
39+
let jsonDecoder = JSONDecoder()
40+
let input = try jsonDecoder.decode(T.self, from: self)
41+
return input
42+
}
43+
44+
func jsonObject() throws -> [String: Any] {
45+
let jsonObject = try JSONSerialization.jsonObject(with: self)
46+
guard let payload = jsonObject as? [String: Any] else {
47+
throw SprinterError.invalidJSON
48+
}
49+
return payload
50+
}
51+
52+
init(jsonObject: [String: Any]) throws {
53+
self = try JSONSerialization.data(withJSONObject: jsonObject)
54+
}
3655

3756
var byteBuffer: ByteBuffer {
3857
var buffer = ByteBufferAllocator().buffer(capacity: self.count)

Tests/LambdaSwiftSprinterNioPluginTests/LambdaApiNIOTests.swift

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import XCTest
2121

2222

2323
@testable import LambdaSwiftSprinterNioPlugin
24-
// FIXME: Add Unit test
2524

2625
final class LambdaApiNIOTests: XCTestCase {
2726

@@ -157,10 +156,10 @@ final class LambdaApiNIOTests: XCTestCase {
157156
}
158157

159158
static var allTests = [
159+
("testGetNextInvocation", testGetNextInvocation),
160160
("testInit", testInit),
161-
("testGetNextInvocation", testGetNextInvocation),
162-
("testPostInvocationResponse", testPostInvocationResponse),
163-
("testPostInvocationError", testPostInvocationError),
164161
("testPostInitializationError", testPostInitializationError),
162+
("testPostInvocationError", testPostInvocationError),
163+
("testPostInvocationResponse", testPostInvocationResponse),
165164
]
166165
}

0 commit comments

Comments
 (0)