Skip to content

Commit 2f3aed0

Browse files
authored
Initial Implementation (#4)
* Define package targets * Protocols and extensions * Helper types * Additional documentation, tweaks * Improve error descriptions * Improve documentation * Generalize default implementations * Remove unused annotation * Add documentation catalog * Error improvements * Additional RuntimeContext members * FunctionContext -> EnvironmentValueProvider * Add InitializationContext protocol * Add Coding protocol, APIGateway implementation * Remove EventHandler, HandlerProvider * Refactor targets * Remove main and mocks for now * Additional helpers * Implement main library * Implement mocks * Fix JSON
1 parent 62730bf commit 2f3aed0

File tree

21 files changed

+731
-0
lines changed

21 files changed

+731
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,6 @@ fastlane/test_output
8888
# https://github.com/johnno1962/injectionforxcode
8989

9090
iOSInjectionProject/
91+
92+
# IDEs and Editors
93+
.vscode

.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<key>FILEHEADER</key>
6+
<string>
7+
// ___FILENAME___
8+
// LambdaExtras
9+
//
10+
// Created by ___FULLUSERNAME___ on ___DATE___.
11+
//</string>
12+
</dict>
13+
</plist>

Package.resolved

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

Package.swift

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// swift-tools-version: 5.7
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "swift-lambda-extras",
8+
platforms: [.macOS(.v12)],
9+
products: [
10+
.library(name: "LambdaExtras", targets: ["LambdaExtras"]),
11+
// Core library; does not link AWS products.
12+
.library(name: "LambdaExtrasCore", targets: ["LambdaExtrasCore"]),
13+
// Testing helpers
14+
.library(name: "LambdaMocks", targets: ["LambdaMocks"])
15+
],
16+
dependencies: [
17+
.package(url: "https://github.com/apple/swift-log.git", .upToNextMajor(from: "1.4.2")),
18+
.package(url: "https://github.com/apple/swift-nio.git", .upToNextMajor(from: "2.43.1")),
19+
.package(url: "https://github.com/swift-server/swift-aws-lambda-events", branch: "main"),
20+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime", branch: "main")
21+
],
22+
targets: [
23+
.target(
24+
name: "LambdaExtrasCore",
25+
dependencies: [
26+
.product(name: "Logging", package: "swift-log"),
27+
.product(name: "NIOCore", package: "swift-nio")
28+
]
29+
),
30+
.target(
31+
name: "LambdaExtras",
32+
dependencies: [
33+
"LambdaExtrasCore",
34+
.product(name: "AWSLambdaRuntime",package: "swift-aws-lambda-runtime"),
35+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events")
36+
]
37+
),
38+
.target(
39+
name: "LambdaMocks",
40+
dependencies: [
41+
"LambdaExtrasCore"
42+
]
43+
),
44+
.testTarget(
45+
name: "LambdaExtrasTests",
46+
dependencies: [
47+
"LambdaExtras"
48+
]
49+
)
50+
]
51+
)
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
//
2+
// APIGatewayCoder.swift
3+
// LambdaExtras
4+
//
5+
// Created by Mathew Gacy on 12/14/23.
6+
//
7+
8+
import AWSLambdaEvents
9+
import AWSLambdaRuntime
10+
import Foundation
11+
import LambdaExtrasCore
12+
13+
/// A coder for APIGateway events.
14+
public struct APIGatewayCoder<E, O>: LambdaCoding where E: Codable, E: Sendable, O: Sendable {
15+
/// A JSON decoder.
16+
let decoder: JSONDecoder
17+
18+
/// A closure returning a response body from the given underlying output.
19+
let responseBodyProvider: @Sendable (O) -> String?
20+
21+
/// Creates an instance.
22+
///
23+
/// - Parameters:
24+
/// - decoder: A JSON decoder.
25+
/// - responseBody: A closure returning a response body from the given output.
26+
public init(
27+
decoder: JSONDecoder = .init(),
28+
responseBody: @escaping @Sendable (O) -> String? = { _ in nil }
29+
) {
30+
self.decoder = decoder
31+
self.responseBodyProvider = responseBody
32+
}
33+
34+
public func decode(event: APIGatewayV2Request) async throws -> E {
35+
guard let body = event.body else {
36+
throw HandlerError.emptyBody
37+
}
38+
39+
return try decoder.decode(E.self, from: body)
40+
}
41+
42+
public func encode(output: O) throws -> APIGatewayV2Response {
43+
APIGatewayV2Response(
44+
statusCode: .ok,
45+
body: responseBodyProvider(output))
46+
}
47+
48+
public func encode(error: Error) throws -> APIGatewayV2Response {
49+
switch error {
50+
case HandlerError.emptyBody:
51+
return APIGatewayV2Response(
52+
statusCode: .badRequest,
53+
body: errorResponseBody(error.localizedDescription))
54+
55+
case HandlerError.envError:
56+
return APIGatewayV2Response(
57+
statusCode: .internalServerError,
58+
body: errorResponseBody(error.localizedDescription))
59+
60+
case HandlerError.custom(let message):
61+
return APIGatewayV2Response(
62+
statusCode: .internalServerError,
63+
body: message.flatMap(errorResponseBody))
64+
65+
default:
66+
return APIGatewayV2Response(
67+
statusCode: .internalServerError,
68+
body: errorResponseBody(error.localizedDescription))
69+
}
70+
}
71+
}
72+
73+
private extension APIGatewayCoder {
74+
/// Returns a response body for the given error.
75+
///
76+
/// - Parameter message: The error message
77+
/// - Returns: The response body.
78+
private func errorResponseBody(_ message: String) -> String {
79+
"""
80+
{"reason": "\(message)"}
81+
"""
82+
}
83+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// DefaultEnvironment.swift
3+
// LambdaExtras
4+
//
5+
// Created by Mathew Gacy on 12/14/23.
6+
//
7+
8+
import Foundation
9+
10+
/// The default lambda environment.
11+
public enum DefaultEnvironment: String {
12+
/// The log level of the lambda's logger.
13+
case logLevel = "LOG_LEVEL"
14+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# ``LambdaExtras``
2+
3+
Swifty helpers for working with AWS Lambda.
4+
5+
## Overview
6+
7+
`LambdaExtras` provides helper types that make it easier to create AWS Lambdas.
8+
9+
## Topics
10+
11+
### Essentials

Sources/LambdaExtras/Exports.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
@_exported import LambdaExtrasCore
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Extensions.swift
3+
// LambdaExtras
4+
//
5+
// Created by Mathew Gacy on 12/15/23.
6+
//
7+
8+
import AWSLambdaRuntime
9+
import AWSLambdaRuntimeCore
10+
import Foundation
11+
import LambdaExtrasCore
12+
import NIOCore
13+
14+
public extension EnvironmentValueProvider where EnvironmentVariable == String {
15+
/// Returns the value of the given environment variable.
16+
///
17+
/// - Parameter environmentVariable: The environment variable whose value should be returned.
18+
func value(for environmentVariable: EnvironmentVariable) throws -> String {
19+
guard let value = Lambda.env(environmentVariable) else {
20+
throw HandlerError.envError(environmentVariable)
21+
}
22+
23+
return value
24+
}
25+
}
26+
27+
public extension EnvironmentValueProvider where EnvironmentVariable: RawRepresentable<String> {
28+
/// Returns the value of the given environment variable.
29+
///
30+
/// - Parameter environmentVariable: The environment variable whose value should be returned.
31+
func value(for environmentVariable: EnvironmentVariable) throws -> String {
32+
guard let value = Lambda.env(environmentVariable.rawValue) else {
33+
throw HandlerError.envError(environmentVariable.rawValue)
34+
}
35+
36+
return value
37+
}
38+
}
39+
40+
extension LambdaContext: RuntimeContext {}
41+
42+
extension LambdaInitializationContext: InitializationContext {
43+
public func handleShutdown(_ handler: @escaping (EventLoop) -> EventLoopFuture<Void>) {
44+
terminator.register(name: "shutdown", handler: handler)
45+
}
46+
}

0 commit comments

Comments
 (0)