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

Commit b32d53c

Browse files
committed
Body parsing is now handled by DecodableBody
1 parent a09988e commit b32d53c

12 files changed

+164
-51
lines changed

Package.swift

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,22 @@ let package = Package(
2323
],
2424
targets: [
2525
.target(
26-
name: "LambdaRuntime",
27-
dependencies: ["AsyncHTTPClient", "NIO", "NIOHTTP1", "NIOFoundationCompat", "Logging", "Base64Kit"]
28-
),
26+
name: "LambdaRuntime", dependencies: [
27+
"AsyncHTTPClient",
28+
"NIO",
29+
"NIOHTTP1",
30+
"NIOFoundationCompat",
31+
"Logging",
32+
"Base64Kit"
33+
]),
2934
.target(
3035
name: "LambdaRuntimeTestUtils",
3136
dependencies: ["NIOHTTP1", "LambdaRuntime"]
3237
),
3338
.testTarget(name: "LambdaRuntimeTests", dependencies: [
3439
"LambdaRuntime",
3540
"LambdaRuntimeTestUtils",
41+
"Base64Kit",
3642
"NIOTestUtils",
3743
"Logging",
3844
])

Sources/LambdaRuntime/Context.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class Context {
1212
public let traceId : String
1313
public let requestId : String
1414

15-
public let logger : Logger
15+
public let logger : Logger
1616
public let eventLoop : EventLoop
1717
public let deadlineDate: Date
1818

Sources/LambdaRuntime/Events/ALB.swift

+5-3
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import NIOHTTP1
88
public struct ALB {
99

1010
/// ALBTargetGroupRequest contains data originating from the ALB Lambda target group integration
11-
public struct TargetGroupRequest {
11+
public struct TargetGroupRequest: DecodableBody {
1212

1313
/// ALBTargetGroupRequestContext contains the information to identify the load balancer invoking the lambda
1414
public struct Context: Codable {
@@ -21,7 +21,7 @@ public struct ALB {
2121
public let headers: HTTPHeaders
2222
public let requestContext: Context
2323
public let isBase64Encoded: Bool
24-
public let body: String
24+
public let body: String?
2525
}
2626

2727
/// ELBContext contains the information to identify the ARN invoking the lambda
@@ -143,7 +143,9 @@ extension ALB.TargetGroupRequest: Decodable {
143143

144144
self.requestContext = try container.decode(Context.self, forKey: .requestContext)
145145
self.isBase64Encoded = try container.decode(Bool.self, forKey: .isBase64Encoded)
146-
self.body = try container.decode(String.self, forKey: .body)
146+
147+
let body = try container.decode(String.self, forKey: .body)
148+
self.body = body != "" ? body : nil
147149
}
148150

149151
}

Sources/LambdaRuntime/Events/APIGateway.swift

+3-12
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Base64Kit
99
public struct APIGateway {
1010

1111
/// APIGatewayRequest contains data coming from the API Gateway
12-
public struct Request {
12+
public struct Request: DecodableBody {
1313

1414
public struct Context: Codable {
1515

@@ -162,18 +162,9 @@ extension APIGateway.Request: Decodable {
162162

163163
extension APIGateway.Request {
164164

165+
@available(*, deprecated, renamed: "decodeBody(_:decoder:)")
165166
public func payload<Payload: Decodable>(_ type: Payload.Type, decoder: JSONDecoder = JSONDecoder()) throws -> Payload {
166-
let body = self.body ?? ""
167-
168-
let capacity = body.lengthOfBytes(using: .utf8)
169-
170-
// TBD: I am pretty sure, we don't need this buffer copy here.
171-
// Access the strings buffer directly to get to the data.
172-
var buffer = ByteBufferAllocator().buffer(capacity: capacity)
173-
buffer.setString(body, at: 0)
174-
buffer.moveWriterIndex(to: capacity)
175-
176-
return try decoder.decode(Payload.self, from: buffer)
167+
return try self.decodeBody(Payload.self, decoder: decoder)
177168
}
178169
}
179170

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import Foundation
2+
import NIO
3+
import NIOFoundationCompat
4+
5+
protocol DecodableBody {
6+
7+
var body: String? { get }
8+
var isBase64Encoded: Bool { get }
9+
10+
}
11+
12+
extension DecodableBody {
13+
14+
var isBase64Encoded: Bool {
15+
return false
16+
}
17+
18+
}
19+
20+
extension DecodableBody {
21+
22+
func decodeBody<T: Decodable>(_ type: T.Type, decoder: JSONDecoder = JSONDecoder()) throws -> T {
23+
24+
// I would really like to not use Foundation.Data at all, but well
25+
// the NIOFoundationCompat just creates an internal Data as well.
26+
// So let's save one malloc and copy and just use Data.
27+
let payload = self.body ?? ""
28+
29+
let data: Data
30+
if self.isBase64Encoded {
31+
let bytes = try payload.base64decoded()
32+
data = Data(bytes)
33+
}
34+
else {
35+
// TBD: Can this ever fail? I wouldn't think so...
36+
data = payload.data(using: .utf8)!
37+
}
38+
39+
return try decoder.decode(T.self, from: data)
40+
}
41+
}

Sources/LambdaRuntime/Events/SNS.swift

+7-12
Original file line numberDiff line numberDiff line change
@@ -100,20 +100,15 @@ extension SNS.Message: Decodable {
100100

101101
}
102102

103-
extension SNS.Message {
103+
extension SNS.Message: DecodableBody {
104104

105+
public var body: String? {
106+
return self.message != "" ? self.message : nil
107+
}
108+
109+
@available(*, deprecated, renamed: "decodeBody(_:decoder:)")
105110
public func payload<Payload: Decodable>(decoder: JSONDecoder = JSONDecoder()) throws -> Payload {
106-
let body = self.message
107-
108-
let capacity = body.lengthOfBytes(using: .utf8)
109-
110-
// TBD: I am pretty sure, we don't need this buffer copy here.
111-
// Access the strings buffer directly to get to the data.
112-
var buffer = ByteBufferAllocator().buffer(capacity: capacity)
113-
buffer.setString(body, at: 0)
114-
buffer.moveWriterIndex(to: capacity)
115-
116-
return try decoder.decode(Payload.self, from: buffer)
111+
return try self.decodeBody(Payload.self, decoder: decoder)
117112
}
118113
}
119114

Sources/LambdaRuntime/Events/SQS.swift

+36-15
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ import NIO
44
/// https://github.com/aws/aws-lambda-go/blob/master/events/sqs.go
55
public struct SQS {
66

7-
public struct Event: Codable {
7+
public struct Event: Decodable {
88
public let records: [Message]
99

1010
enum CodingKeys: String, CodingKey {
1111
case records = "Records"
1212
}
1313
}
1414

15-
public struct Message: Codable {
15+
public struct Message: DecodableBody {
1616

1717
/// https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_MessageAttributeValue.html
1818
public enum Attribute {
@@ -23,28 +23,49 @@ public struct SQS {
2323

2424
public let messageId : String
2525
public let receiptHandle : String
26-
public let body : String
26+
public let body : String?
2727
public let md5OfBody : String
2828
public let md5OfMessageAttributes : String?
2929
public let attributes : [String: String]
3030
public let messageAttributes : [String: Attribute]
3131
public let eventSourceArn : String
3232
public let eventSource : String
3333
public let awsRegion : String
34+
}
35+
}
36+
37+
extension SQS.Message: Decodable {
38+
39+
enum CodingKeys: String, CodingKey {
40+
case messageId
41+
case receiptHandle
42+
case body
43+
case md5OfBody
44+
case md5OfMessageAttributes
45+
case attributes
46+
case messageAttributes
47+
case eventSourceArn = "eventSourceARN"
48+
case eventSource
49+
case awsRegion
50+
}
51+
52+
public init(from decoder: Decoder) throws {
53+
54+
let container = try decoder.container(keyedBy: CodingKeys.self)
55+
self.messageId = try container.decode(String.self, forKey: .messageId)
56+
self.receiptHandle = try container.decode(String.self, forKey: .receiptHandle)
57+
self.md5OfBody = try container.decode(String.self, forKey: .md5OfBody)
58+
self.md5OfMessageAttributes = try container.decodeIfPresent(String.self, forKey: .md5OfMessageAttributes)
59+
self.attributes = try container.decode([String: String].self, forKey: .attributes)
60+
self.messageAttributes = try container.decode([String: Attribute].self, forKey: .messageAttributes)
61+
self.eventSourceArn = try container.decode(String.self, forKey: .eventSourceArn)
62+
self.eventSource = try container.decode(String.self, forKey: .eventSource)
63+
self.awsRegion = try container.decode(String.self, forKey: .awsRegion)
3464

35-
enum CodingKeys: String, CodingKey {
36-
case messageId
37-
case receiptHandle
38-
case body
39-
case md5OfBody
40-
case md5OfMessageAttributes
41-
case attributes
42-
case messageAttributes
43-
case eventSourceArn = "eventSourceARN"
44-
case eventSource
45-
case awsRegion
46-
}
65+
let body = try container.decode(String?.self, forKey: .body)
66+
self.body = body != "" ? body : nil
4767
}
68+
4869
}
4970

5071
extension SQS.Message.Attribute: Equatable { }

Tests/LambdaRuntimeTests/Events/ALBTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ALBTests: XCTestCase {
4040
let event = try decoder.decode(ALB.TargetGroupRequest.self, from: data)
4141

4242
XCTAssertEqual(event.httpMethod, .GET)
43-
XCTAssertEqual(event.body, "")
43+
XCTAssertEqual(event.body, nil)
4444
XCTAssertEqual(event.isBase64Encoded, false)
4545
XCTAssertEqual(event.headers.count, 11)
4646
XCTAssertEqual(event.path, "/")

Tests/LambdaRuntimeTests/Events/APIGatewayTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ import XCTest
33
import NIO
44
import NIOHTTP1
55
import NIOFoundationCompat
6-
import LambdaRuntimeTestUtils
76
@testable import LambdaRuntime
7+
import LambdaRuntimeTestUtils
88

99
class APIGatewayTests: XCTestCase {
1010

@@ -85,7 +85,7 @@ class APIGatewayTests: XCTestCase {
8585
XCTAssertEqual(request.path, "/todos")
8686
XCTAssertEqual(request.httpMethod, .POST)
8787

88-
let todo = try request.payload(Todo.self)
88+
let todo = try request.decodeBody(Todo.self)
8989
XCTAssertEqual(todo.title, "a todo")
9090
}
9191
catch {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import Foundation
2+
import XCTest
3+
import Base64Kit
4+
@testable import LambdaRuntime
5+
6+
class DecodableBodyTests: XCTestCase {
7+
8+
struct TestEvent: DecodableBody {
9+
let body: String?
10+
let isBase64Encoded: Bool
11+
}
12+
13+
struct TestPayload: Codable {
14+
let hello: String
15+
}
16+
17+
func testSimplePayloadFromEvent() {
18+
do {
19+
let event = TestEvent(body: "{\"hello\":\"world\"}", isBase64Encoded: false)
20+
let payload = try event.decodeBody(TestPayload.self)
21+
22+
XCTAssertEqual(payload.hello, "world")
23+
}
24+
catch {
25+
XCTFail("Unexpected error: \(error)")
26+
}
27+
}
28+
29+
func testBase64PayloadFromEvent() {
30+
do {
31+
let event = TestEvent(body: "eyJoZWxsbyI6IndvcmxkIn0=", isBase64Encoded: true)
32+
let payload = try event.decodeBody(TestPayload.self)
33+
34+
XCTAssertEqual(payload.hello, "world")
35+
}
36+
catch {
37+
XCTFail("Unexpected error: \(error)")
38+
}
39+
}
40+
41+
func testNoDataFromEvent() {
42+
do {
43+
let event = TestEvent(body: "", isBase64Encoded: false)
44+
_ = try event.decodeBody(TestPayload.self)
45+
46+
XCTFail("Did not expect to reach this point")
47+
}
48+
catch DecodingError.dataCorrupted(_) {
49+
return // expected error
50+
}
51+
catch {
52+
XCTFail("Unexpected error: \(error)")
53+
}
54+
55+
}
56+
57+
}

Tests/LambdaRuntimeTests/Events/SNSTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ class SNSTests: XCTestCase {
7373
XCTAssertEqual(record.sns.messageAttributes["binary"], .binary(binaryBuffer))
7474
XCTAssertEqual(record.sns.messageAttributes["string"], .string("abc123"))
7575

76-
let payload: TestStruct = try record.sns.payload()
76+
let payload = try record.sns.decodeBody(TestStruct.self)
7777
XCTAssertEqual(payload.hello, "world")
7878
}
7979
catch {

Tests/LambdaRuntimeTests/Runtime+CodableTests.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import Foundation
22
import XCTest
33
import NIO
44
import NIOHTTP1
5-
import LambdaRuntimeTestUtils
65
@testable import LambdaRuntime
6+
import LambdaRuntimeTestUtils
77

88
class RuntimeCodableTests: XCTestCase {
99

0 commit comments

Comments
 (0)