Skip to content

Commit db7f9e6

Browse files
committed
Added AWSLambdaEvents library
1 parent 2cd6bce commit db7f9e6

22 files changed

+2593
-0
lines changed

Package.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ let package = Package(
66
name: "swift-aws-lambda-runtime",
77
products: [
88
.library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]),
9+
.library(name: "AWSLambdaEvents", targets: ["AWSLambdaEvents"]),
910
],
1011
dependencies: [
1112
.package(url: "https://github.com/apple/swift-nio.git", from: "2.8.0"),
@@ -20,6 +21,11 @@ let package = Package(
2021
.product(name: "NIOFoundationCompat", package: "swift-nio"),
2122
]),
2223
.testTarget(name: "AWSLambdaRuntimeTests", dependencies: ["AWSLambdaRuntime"]),
24+
.target(name: "AWSLambdaEvents", dependencies: [
25+
.product(name: "NIOHTTP1", package: "swift-nio"),
26+
.product(name: "NIOFoundationCompat", package: "swift-nio"),
27+
]),
28+
.testTarget(name: "AWSLambdaEventsTests", dependencies: ["AWSLambdaEvents"]),
2329
// samples
2430
.target(name: "StringSample", dependencies: ["AWSLambdaRuntime"]),
2531
.target(name: "CodableSample", dependencies: ["AWSLambdaRuntime"]),

Sources/AWSLambdaEvents/ALB.swift

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2017-2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftAWSLambdaRuntime project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import class Foundation.JSONEncoder
16+
import NIO
17+
import NIOFoundationCompat
18+
import NIOHTTP1
19+
20+
// https://github.com/aws/aws-lambda-go/blob/master/events/alb.go
21+
public enum ALB {
22+
/// ALBTargetGroupRequest contains data originating from the ALB Lambda target group integration
23+
public struct TargetGroupRequest: DecodableBody {
24+
/// ALBTargetGroupRequestContext contains the information to identify the load balancer invoking the lambda
25+
public struct Context: Codable {
26+
public let elb: ELBContext
27+
}
28+
29+
public let httpMethod: HTTPMethod
30+
public let path: String
31+
public let queryStringParameters: [String: [String]]
32+
public let headers: HTTPHeaders
33+
public let requestContext: Context
34+
public let isBase64Encoded: Bool
35+
public let body: String?
36+
}
37+
38+
/// ELBContext contains the information to identify the ARN invoking the lambda
39+
public struct ELBContext: Codable {
40+
public let targetGroupArn: String
41+
}
42+
43+
public struct TargetGroupResponse {
44+
public let statusCode: HTTPResponseStatus
45+
public let statusDescription: String?
46+
public let headers: HTTPHeaders?
47+
public let body: String
48+
public let isBase64Encoded: Bool
49+
50+
public init(
51+
statusCode: HTTPResponseStatus,
52+
statusDescription: String? = nil,
53+
headers: HTTPHeaders? = nil,
54+
body: String = "",
55+
isBase64Encoded: Bool = false
56+
) {
57+
self.statusCode = statusCode
58+
self.statusDescription = statusDescription
59+
self.headers = headers
60+
self.body = body
61+
self.isBase64Encoded = isBase64Encoded
62+
}
63+
}
64+
}
65+
66+
// MARK: - Request -
67+
68+
extension ALB.TargetGroupRequest: Decodable {
69+
enum CodingKeys: String, CodingKey {
70+
case httpMethod
71+
case path
72+
case queryStringParameters
73+
case multiValueQueryStringParameters
74+
case headers
75+
case multiValueHeaders
76+
case requestContext
77+
case isBase64Encoded
78+
case body
79+
}
80+
81+
public init(from decoder: Decoder) throws {
82+
let container = try decoder.container(keyedBy: CodingKeys.self)
83+
84+
let method = try container.decode(String.self, forKey: .httpMethod)
85+
self.httpMethod = HTTPMethod(rawValue: method)
86+
87+
self.path = try container.decode(String.self, forKey: .path)
88+
89+
// crazy multiple headers
90+
// https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers
91+
92+
if let multiValueQueryStringParameters =
93+
try container.decodeIfPresent([String: [String]].self, forKey: .multiValueQueryStringParameters) {
94+
self.queryStringParameters = multiValueQueryStringParameters
95+
} else {
96+
let singleValueQueryStringParameters = try container.decode(
97+
[String: String].self,
98+
forKey: .queryStringParameters
99+
)
100+
self.queryStringParameters = singleValueQueryStringParameters.mapValues { [$0] }
101+
}
102+
103+
if let multiValueHeaders =
104+
try container.decodeIfPresent([String: [String]].self, forKey: .multiValueHeaders) {
105+
self.headers = HTTPHeaders(awsHeaders: multiValueHeaders)
106+
} else {
107+
let singleValueHeaders = try container.decode(
108+
[String: String].self,
109+
forKey: .headers
110+
)
111+
let multiValueHeaders = singleValueHeaders.mapValues { [$0] }
112+
self.headers = HTTPHeaders(awsHeaders: multiValueHeaders)
113+
}
114+
115+
self.requestContext = try container.decode(Context.self, forKey: .requestContext)
116+
self.isBase64Encoded = try container.decode(Bool.self, forKey: .isBase64Encoded)
117+
118+
let body = try container.decode(String.self, forKey: .body)
119+
self.body = body != "" ? body : nil
120+
}
121+
}
122+
123+
// MARK: - Response -
124+
125+
extension ALB.TargetGroupResponse: Encodable {
126+
static let MultiValueHeadersEnabledKey =
127+
CodingUserInfoKey(rawValue: "ALB.TargetGroupResponse.MultiValueHeadersEnabledKey")!
128+
129+
enum CodingKeys: String, CodingKey {
130+
case statusCode
131+
case statusDescription
132+
case headers
133+
case multiValueHeaders
134+
case body
135+
case isBase64Encoded
136+
}
137+
138+
public func encode(to encoder: Encoder) throws {
139+
var container = encoder.container(keyedBy: CodingKeys.self)
140+
try container.encode(statusCode.code, forKey: .statusCode)
141+
142+
let multiValueHeaderSupport =
143+
encoder.userInfo[ALB.TargetGroupResponse.MultiValueHeadersEnabledKey] as? Bool ?? false
144+
145+
switch (multiValueHeaderSupport, headers) {
146+
case (true, .none):
147+
try container.encode([String: String](), forKey: .multiValueHeaders)
148+
case (false, .none):
149+
try container.encode([String: [String]](), forKey: .headers)
150+
case (true, .some(let headers)):
151+
var multiValueHeaders: [String: [String]] = [:]
152+
headers.forEach { name, value in
153+
var values = multiValueHeaders[name] ?? []
154+
values.append(value)
155+
multiValueHeaders[name] = values
156+
}
157+
try container.encode(multiValueHeaders, forKey: .multiValueHeaders)
158+
case (false, .some(let headers)):
159+
var singleValueHeaders: [String: String] = [:]
160+
headers.forEach { name, value in
161+
singleValueHeaders[name] = value
162+
}
163+
try container.encode(singleValueHeaders, forKey: .headers)
164+
}
165+
166+
try container.encodeIfPresent(statusDescription, forKey: .statusDescription)
167+
try container.encodeIfPresent(body, forKey: .body)
168+
try container.encodeIfPresent(isBase64Encoded, forKey: .isBase64Encoded)
169+
}
170+
}
171+
172+
extension ALB.TargetGroupResponse {
173+
public init<Payload: Encodable>(
174+
statusCode: HTTPResponseStatus,
175+
statusDescription: String? = nil,
176+
headers: HTTPHeaders? = nil,
177+
payload: Payload,
178+
encoder: JSONEncoder = JSONEncoder()
179+
) throws {
180+
var headers = headers ?? HTTPHeaders()
181+
headers.add(name: "Content-Type", value: "application/json")
182+
183+
self.statusCode = statusCode
184+
self.statusDescription = statusDescription
185+
self.headers = headers
186+
187+
var buffer = try encoder.encodeAsByteBuffer(payload, allocator: ByteBufferAllocator())
188+
guard let string = buffer.readString(length: buffer.readableBytes) else {
189+
fatalError("buffer.readString(length: buffer.readableBytes) failed")
190+
}
191+
self.body = string
192+
self.isBase64Encoded = false
193+
}
194+
195+
/// Use this method to send any arbitrary byte buffer back to the API Gateway.
196+
/// Sadly Apple currently doesn't seem to be confident enough to advertise
197+
/// their base64 implementation publically. SAD. SO SAD. Therefore no
198+
/// ByteBuffer for you my friend.
199+
public init(
200+
statusCode: HTTPResponseStatus,
201+
statusDescription: String? = nil,
202+
headers: HTTPHeaders? = nil,
203+
buffer: NIO.ByteBuffer
204+
) {
205+
let headers = headers ?? HTTPHeaders()
206+
207+
self.statusCode = statusCode
208+
self.statusDescription = statusDescription
209+
self.headers = headers
210+
self.body = buffer.withUnsafeReadableBytes { (ptr) -> String in
211+
String(base64Encoding: ptr)
212+
}
213+
self.isBase64Encoded = true
214+
}
215+
}

0 commit comments

Comments
 (0)