Skip to content
This repository was archived by the owner on Jul 14, 2020. It is now read-only.

Commit 2c54662

Browse files
authored
Lambda does not require to have response. (#7)
* Lambda handler has the option to not return a ByteBuffer. (empty result) * Tested if we can send nothing as the response.
1 parent c8641d4 commit 2c54662

File tree

11 files changed

+145
-50
lines changed

11 files changed

+145
-50
lines changed

Examples/SquareNumber/Sources/SquareNumber/main.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,11 @@ func squareNumber(input: Input, context: Context) -> Output {
1414
return Output(result: squaredNumber)
1515
}
1616

17+
func printNumber(input: Input, context: Context) -> EventLoopFuture<Void> {
18+
context.logger.info("Number is: \(input.number)")
19+
return context.eventLoop.makeSucceededFuture(Void())
20+
}
21+
1722
let group = MultiThreadedEventLoopGroup(numberOfThreads: 1)
1823
defer {
1924
try! group.syncShutdownGracefully()
@@ -24,6 +29,7 @@ do {
2429
defer { try! runtime.syncShutdown() }
2530

2631
runtime.register(for: "squareNumber", handler: Runtime.codable(squareNumber))
32+
runtime.register(for: "printNumber", handler: Runtime.codable(printNumber))
2733
try runtime.start().wait()
2834
}
2935
catch {

Examples/SquareNumber/template.yaml

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,15 @@ Resources:
2727
Handler: "SquareNumber.squareNumber"
2828
Runtime: provided
2929
Layers:
30-
- !Ref SwiftLayer
31-
Events:
32-
HelloWorld:
33-
Type: Api
34-
Properties:
35-
Path: /hello
36-
Method: get
37-
30+
- !Ref SwiftLayer
31+
32+
PrintNumberFunction:
33+
Type: AWS::Serverless::Function
34+
Properties:
35+
CodeUri: Examples/SquareNumber/lambda.zip
36+
Handler: "SquareNumber.printNumber"
37+
Runtime: provided
38+
Layers:
39+
- !Ref SwiftLayer
40+
41+

Examples/TodoAPIGateway/Package.resolved

Lines changed: 10 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Sources/AWSLambda/Runtime+Codable.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@ extension Runtime {
77
/// wrapper to use for the register function that wraps the encoding and decoding
88
public static func codable<Event: Decodable, Result: Encodable>(
99
_ handler: @escaping (Event, Context) -> EventLoopFuture<Result>)
10-
-> ((NIO.ByteBuffer, Context) -> EventLoopFuture<ByteBuffer>)
10+
-> ((NIO.ByteBuffer, Context) -> EventLoopFuture<ByteBuffer?>)
1111
{
12-
return { (inputBytes: NIO.ByteBuffer, ctx: Context) -> EventLoopFuture<ByteBuffer> in
12+
return { (inputBytes: NIO.ByteBuffer, ctx: Context) -> EventLoopFuture<ByteBuffer?> in
1313
let input: Event
1414
do {
1515
input = try JSONDecoder().decode(Event.self, from: inputBytes)
@@ -25,10 +25,27 @@ extension Runtime {
2525
}
2626
}
2727

28+
public static func codable<Event: Decodable>(
29+
_ handler: @escaping (Event, Context) -> EventLoopFuture<Void>)
30+
-> ((NIO.ByteBuffer, Context) -> EventLoopFuture<ByteBuffer?>)
31+
{
32+
return { (inputBytes: NIO.ByteBuffer, ctx: Context) -> EventLoopFuture<ByteBuffer?> in
33+
let input: Event
34+
do {
35+
input = try JSONDecoder().decode(Event.self, from: inputBytes)
36+
}
37+
catch {
38+
return ctx.eventLoop.makeFailedFuture(error)
39+
}
40+
41+
return handler(input, ctx).map { return nil }
42+
}
43+
}
44+
2845
/// synchronous interface to use with codable
2946
public static func codable<Event: Decodable, Result: Encodable>(
3047
_ handler: @escaping (Event, Context) throws -> (Result))
31-
-> ((NIO.ByteBuffer, Context) -> EventLoopFuture<ByteBuffer>)
48+
-> ((NIO.ByteBuffer, Context) -> EventLoopFuture<ByteBuffer?>)
3249
{
3350
return Runtime.codable { (event: Event, context) -> EventLoopFuture<Result> in
3451
let promise = context.eventLoop.makePromise(of: Result.self)

Sources/AWSLambda/Runtime.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ final public class Runtime {
6666
}
6767

6868

69-
public typealias Handler = (NIO.ByteBuffer, Context) -> EventLoopFuture<NIO.ByteBuffer>
69+
public typealias Handler = (NIO.ByteBuffer, Context) -> EventLoopFuture<NIO.ByteBuffer?>
7070

7171
/// Registers a handler function for execution by the runtime. This method is
7272
/// not thread safe. Therefore it is only safe to invoke this function before
@@ -118,6 +118,12 @@ final public class Runtime {
118118

119119
guard let handler = self.handlers[self.handlerName] else {
120120
return self.runtimeLoop.makeFailedFuture(RuntimeError.unknownLambdaHandler(self.handlerName))
121+
.flatMapError { (error) -> EventLoopFuture<Void> in
122+
return self.client.postInvocationError(for: context.requestId, error: error)
123+
}
124+
.flatMapErrorThrowing { (error) in
125+
context.logger.error("Could not post lambda result to runtime. error: \(error)")
126+
}
121127
}
122128

123129
return handler(byteBuffer, context)
@@ -144,4 +150,3 @@ final public class Runtime {
144150
}
145151
}
146152
}
147-

Sources/AWSLambda/RuntimeAPIClient.swift

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public struct Invocation {
5151
protocol LambdaRuntimeAPI {
5252

5353
func getNextInvocation() -> EventLoopFuture<(Invocation, NIO.ByteBuffer)>
54-
func postInvocationResponse(for requestId: String, httpBody: NIO.ByteBuffer) -> EventLoopFuture<Void>
54+
func postInvocationResponse(for requestId: String, httpBody: NIO.ByteBuffer?) -> EventLoopFuture<Void>
5555
func postInvocationError(for requestId: String, error: Error) -> EventLoopFuture<Void>
5656

5757
func syncShutdown() throws
@@ -86,14 +86,15 @@ extension RuntimeAPIClient: LambdaRuntimeAPI {
8686
guard let data = response.body else {
8787
throw RuntimeError.invocationMissingData
8888
}
89-
89+
9090
return (try Invocation(headers: response.headers), data)
9191
}
9292
}
9393

94-
func postInvocationResponse(for requestId: String, httpBody: NIO.ByteBuffer) -> EventLoopFuture<Void> {
94+
func postInvocationResponse(for requestId: String, httpBody: NIO.ByteBuffer?) -> EventLoopFuture<Void> {
9595
let url = "http://\(lambdaRuntimeAPI)/2018-06-01/runtime/invocation/\(requestId)/response"
96-
return self.httpClient.post(url: url, body: .byteBuffer(httpBody))
96+
let body = httpBody != nil ? HTTPClient.Body.byteBuffer(httpBody!) : nil
97+
return self.httpClient.post(url: url, body: body)
9798
.map { (_) -> Void in }
9899
}
99100

Sources/AWSLambda/RuntimeError.swift

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,4 @@ enum RuntimeError: Error, Equatable {
1010
case unknownLambdaHandler(String)
1111

1212
case endpointError(String)
13-
14-
1513
}

Tests/AWSLambdaTests/Runtime+CodableTests.swift

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class RuntimeCodableTests: XCTestCase {
2222
let greeting: String
2323
}
2424

25-
func testHappyPath() {
25+
func testCodableHandlerWithResultSuccess() {
2626
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
2727
defer {
2828
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
@@ -35,8 +35,8 @@ class RuntimeCodableTests: XCTestCase {
3535
let inputBytes = try JSONEncoder().encodeAsByteBuffer(TestRequest(name: "world"), allocator: ByteBufferAllocator())
3636
let ctx = try Context(environment: .forTesting(), invocation: .forTesting(), eventLoop: eventLoopGroup.next())
3737

38-
let response = try handler(inputBytes, ctx).flatMapThrowing { (outputBytes) -> TestResponse in
39-
return try JSONDecoder().decode(TestResponse.self, from: outputBytes)
38+
let response = try handler(inputBytes, ctx).flatMapThrowing { (bytes) -> TestResponse in
39+
return try JSONDecoder().decode(TestResponse.self, from: bytes!)
4040
}.wait()
4141

4242
XCTAssertEqual(response, TestResponse(greeting: "Hello world!"))
@@ -46,7 +46,7 @@ class RuntimeCodableTests: XCTestCase {
4646
}
4747
}
4848

49-
func testInvalidJSONInput() {
49+
func testCodableHandlerWithResultInvalidInput() {
5050
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
5151
defer {
5252
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
@@ -62,7 +62,7 @@ class RuntimeCodableTests: XCTestCase {
6262

6363
_ = try handler(inputBytes, ctx).flatMapThrowing { (outputBytes) -> TestResponse in
6464
XCTFail("The function should not be invoked.")
65-
return try JSONDecoder().decode(TestResponse.self, from: outputBytes)
65+
return try JSONDecoder().decode(TestResponse.self, from: outputBytes!)
6666
}.wait()
6767

6868
XCTFail("Did not expect to succeed.")
@@ -75,7 +75,80 @@ class RuntimeCodableTests: XCTestCase {
7575
}
7676
}
7777

78-
func testSynchronousCodableInterface() {
78+
func testCodableHandlerWithoutResultSuccess() {
79+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
80+
defer {
81+
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
82+
}
83+
let handler = Runtime.codable { (req: TestRequest, ctx) -> EventLoopFuture<Void> in
84+
return ctx.eventLoop.makeSucceededFuture(Void())
85+
}
86+
87+
do {
88+
let inputBytes = try JSONEncoder().encodeAsByteBuffer(TestRequest(name: "world"), allocator: ByteBufferAllocator())
89+
let ctx = try Context(environment: .forTesting(), invocation: .forTesting(), eventLoop: eventLoopGroup.next())
90+
91+
_ = try handler(inputBytes, ctx).wait()
92+
}
93+
catch {
94+
XCTFail("Unexpected error: \(error)")
95+
}
96+
}
97+
98+
func testCodableHandlerWithoutResultInvalidInput() {
99+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
100+
defer {
101+
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
102+
}
103+
let handler = Runtime.codable { (req: TestRequest, ctx) -> EventLoopFuture<Void> in
104+
return ctx.eventLoop.makeSucceededFuture(Void())
105+
}
106+
107+
do {
108+
var inputBytes = try JSONEncoder().encodeAsByteBuffer(TestRequest(name: "world"), allocator: ByteBufferAllocator())
109+
inputBytes.setString("asd", at: 0) // destroy the json
110+
let ctx = try Context(environment: .forTesting(), invocation: .forTesting(), eventLoop: eventLoopGroup.next())
111+
112+
_ = try handler(inputBytes, ctx).wait()
113+
114+
XCTFail("Did not expect to succeed.")
115+
}
116+
catch DecodingError.dataCorrupted(_) {
117+
// this is our expected case
118+
}
119+
catch {
120+
XCTFail("Unexpected error: \(error)")
121+
}
122+
}
123+
124+
func testCodableHandlerWithoutResultFailure() {
125+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
126+
defer {
127+
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
128+
}
129+
let handler = Runtime.codable { (req: TestRequest, ctx) -> EventLoopFuture<Void> in
130+
return ctx.eventLoop.makeFailedFuture(RuntimeError.unknown)
131+
}
132+
133+
do {
134+
let inputBytes = try JSONEncoder().encodeAsByteBuffer(TestRequest(name: "world"), allocator: ByteBufferAllocator())
135+
let ctx = try Context(environment: .forTesting(), invocation: .forTesting(), eventLoop: eventLoopGroup.next())
136+
137+
_ = try handler(inputBytes, ctx).wait()
138+
139+
XCTFail("Did not expect to reach this point")
140+
}
141+
catch RuntimeError.unknown {
142+
// expected case
143+
}
144+
catch {
145+
XCTFail("Unexpected error: \(error)")
146+
}
147+
}
148+
149+
// MARK: - Synchrounous Interface -
150+
151+
func testSynchronousCodableInterfaceSuccess() {
79152
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
80153
defer {
81154
XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully())
@@ -88,8 +161,8 @@ class RuntimeCodableTests: XCTestCase {
88161
let inputBytes = try JSONEncoder().encodeAsByteBuffer(TestRequest(name: "world"), allocator: ByteBufferAllocator())
89162
let ctx = try Context(environment: .forTesting(), invocation: .forTesting(), eventLoop: eventLoopGroup.next())
90163

91-
let response = try handler(inputBytes, ctx).flatMapThrowing { (outputBytes) -> TestResponse in
92-
return try JSONDecoder().decode(TestResponse.self, from: outputBytes)
164+
let response = try handler(inputBytes, ctx).flatMapThrowing { (bytes) -> TestResponse in
165+
return try JSONDecoder().decode(TestResponse.self, from: bytes!)
93166
}.wait()
94167

95168
XCTAssertEqual(response, TestResponse(greeting: "Hello world!"))

Tests/AWSLambdaTests/RuntimeTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class RuntimeTests: XCTestCase {
6666
}
6767
}
6868

69-
func testCreateRuntimeMissingHanlder() {
69+
func testCreateRuntimeMissingHandler() {
7070
setenv("AWS_LAMBDA_RUNTIME_API", "localhost", 1)
7171
unsetenv("_HANDLER")
7272

Tests/AWSLambdaTests/Utils/MockLambdaRuntimeAPI.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ extension MockLambdaRuntimeAPI: LambdaRuntimeAPI {
4848
}
4949
}
5050

51-
func postInvocationResponse(for requestId: String, httpBody: ByteBuffer) -> EventLoopFuture<Void> {
51+
func postInvocationResponse(for requestId: String, httpBody: ByteBuffer?) -> EventLoopFuture<Void> {
5252
return self.runLoop.makeSucceededFuture(Void())
5353
}
5454

0 commit comments

Comments
 (0)