Skip to content

Commit 4d1ece8

Browse files
committed
SPI Attempt #1
1 parent 2385a74 commit 4d1ece8

37 files changed

+1140
-1485
lines changed

Package.swift

+9
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,15 @@ let package = Package(
2424
.product(name: "NIOFoundationCompat", package: "swift-nio"),
2525
]),
2626
.target(name: "AWSLambdaRuntimeCore", dependencies: [
27+
.byName(name: "LambdaRuntimeCore"),
28+
.product(name: "Logging", package: "swift-log"),
29+
.product(name: "Backtrace", package: "swift-backtrace"),
30+
.product(name: "NIOHTTP1", package: "swift-nio"),
31+
.product(name: "NIOCore", package: "swift-nio"),
32+
.product(name: "NIOConcurrencyHelpers", package: "swift-nio"),
33+
.product(name: "NIOPosix", package: "swift-nio"),
34+
]),
35+
.target(name: "LambdaRuntimeCore", dependencies: [
2736
.product(name: "Logging", package: "swift-log"),
2837
.product(name: "Backtrace", package: "swift-backtrace"),
2938
.product(name: "NIOHTTP1", package: "swift-nio"),

Sources/AWSLambdaRuntime/Context+Foundation.swift

+3-2
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
import AWSLambdaRuntimeCore
1615
import struct Foundation.Date
16+
@_spi(Lambda) import AWSLambdaRuntimeCore
1717

18-
extension LambdaContext {
18+
@_spi(Lambda)
19+
extension AWSLambda.Context {
1920
var deadlineDate: Date {
2021
let secondsSinceEpoch = Double(Int64(bitPattern: self.deadline.rawValue)) / -1_000_000_000
2122
return Date(timeIntervalSince1970: secondsSinceEpoch)
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@_exported import AWSLambdaRuntimeCore
2+
3+
@_spi(Lambda) import LambdaRuntimeCore
4+
5+
@main
6+
@available(macOS 12.0, *)
7+
struct MyHandler: LambdaHandler {
8+
typealias Context = AWSLambda.Context
9+
typealias Event = String
10+
typealias Output = String
11+
12+
init(context: Lambda.InitializationContext) async throws {}
13+
14+
func handle(_ event: String, context: Context) async throws -> String {
15+
return event
16+
}
17+
}

Sources/AWSLambdaRuntime/Lambda+Codable.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
@_exported import AWSLambdaRuntimeCore
15+
@_spi(Lambda) import LambdaRuntimeCore
1616
import struct Foundation.Data
1717
import class Foundation.JSONDecoder
1818
import class Foundation.JSONEncoder
@@ -22,6 +22,7 @@ import NIOFoundationCompat
2222
// MARK: - Codable support
2323

2424
/// Implementation of a`ByteBuffer` to `Event` decoding
25+
@_spi(Lambda)
2526
extension EventLoopLambdaHandler where Event: Decodable {
2627
@inlinable
2728
public func decode(buffer: ByteBuffer) throws -> Event {
@@ -30,6 +31,7 @@ extension EventLoopLambdaHandler where Event: Decodable {
3031
}
3132

3233
/// Implementation of `Output` to `ByteBuffer` encoding
34+
@_spi(Lambda)
3335
extension EventLoopLambdaHandler where Output: Encodable {
3436
@inlinable
3537
public func encode(allocator: ByteBufferAllocator, value: Output) throws -> ByteBuffer? {
@@ -39,6 +41,7 @@ extension EventLoopLambdaHandler where Output: Encodable {
3941

4042
/// Default `ByteBuffer` to `Event` decoder using Foundation's JSONDecoder
4143
/// Advanced users that want to inject their own codec can do it by overriding these functions.
44+
@_spi(Lambda)
4245
extension EventLoopLambdaHandler where Event: Decodable {
4346
public var decoder: LambdaCodableDecoder {
4447
Lambda.defaultJSONDecoder
@@ -47,6 +50,7 @@ extension EventLoopLambdaHandler where Event: Decodable {
4750

4851
/// Default `Output` to `ByteBuffer` encoder using Foundation's JSONEncoder
4952
/// Advanced users that want to inject their own codec can do it by overriding these functions.
53+
@_spi(Lambda)
5054
extension EventLoopLambdaHandler where Output: Encodable {
5155
public var encoder: LambdaCodableEncoder {
5256
Lambda.defaultJSONEncoder
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
@_exported import LambdaRuntimeCore
2+
@_spi(Lambda) import LambdaRuntimeCore
3+
4+
public enum AWSLambda {}
5+
6+
@_spi(Lambda)
7+
extension AWSLambda: LambdaProvider { }

Sources/AWSLambdaRuntimeCore/ControlPlaneRequest.swift

+45-71
Original file line numberDiff line numberDiff line change
@@ -14,84 +14,58 @@
1414

1515
import NIOCore
1616
import NIOHTTP1
17+
@_spi(Lambda) import LambdaRuntimeCore
1718

18-
enum ControlPlaneRequest: Hashable {
19-
case next
20-
case invocationResponse(LambdaRequestID, ByteBuffer?)
21-
case invocationError(LambdaRequestID, ErrorResponse)
22-
case initializationError(ErrorResponse)
23-
}
24-
25-
enum ControlPlaneResponse: Hashable {
26-
case next(Invocation, ByteBuffer)
27-
case accepted
28-
case error(ErrorResponse)
29-
}
30-
31-
struct Invocation: Hashable {
32-
var requestID: String
33-
var deadlineInMillisSinceEpoch: Int64
34-
var invokedFunctionARN: String
35-
var traceID: String
36-
var clientContext: String?
37-
var cognitoIdentity: String?
19+
@_spi(Lambda)
20+
extension AWSLambda {
21+
public struct Invocation: LambdaInvocation {
22+
public var requestID: String
23+
public var deadlineInMillisSinceEpoch: Int64
24+
public var invokedFunctionARN: String
25+
public var traceID: String
26+
public var clientContext: String?
27+
public var cognitoIdentity: String?
3828

39-
init(requestID: String,
40-
deadlineInMillisSinceEpoch: Int64,
41-
invokedFunctionARN: String,
42-
traceID: String,
43-
clientContext: String?,
44-
cognitoIdentity: String?) {
45-
self.requestID = requestID
46-
self.deadlineInMillisSinceEpoch = deadlineInMillisSinceEpoch
47-
self.invokedFunctionARN = invokedFunctionARN
48-
self.traceID = traceID
49-
self.clientContext = clientContext
50-
self.cognitoIdentity = cognitoIdentity
51-
}
52-
53-
init(headers: HTTPHeaders) throws {
54-
guard let requestID = headers.first(name: AmazonHeaders.requestID), !requestID.isEmpty else {
55-
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.requestID)
29+
init(requestID: String,
30+
deadlineInMillisSinceEpoch: Int64,
31+
invokedFunctionARN: String,
32+
traceID: String,
33+
clientContext: String?,
34+
cognitoIdentity: String?) {
35+
self.requestID = requestID
36+
self.deadlineInMillisSinceEpoch = deadlineInMillisSinceEpoch
37+
self.invokedFunctionARN = invokedFunctionARN
38+
self.traceID = traceID
39+
self.clientContext = clientContext
40+
self.cognitoIdentity = cognitoIdentity
5641
}
5742

58-
guard let deadline = headers.first(name: AmazonHeaders.deadline),
59-
let unixTimeInMilliseconds = Int64(deadline)
60-
else {
61-
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.deadline)
62-
}
43+
public init(headers: HTTPHeaders) throws {
44+
guard let requestID = headers.first(name: AmazonHeaders.requestID), !requestID.isEmpty else {
45+
throw LambdaRuntimeError.invocationHeadMissingRequestID
46+
}
6347

64-
guard let invokedFunctionARN = headers.first(name: AmazonHeaders.invokedFunctionARN) else {
65-
throw Lambda.RuntimeError.invocationMissingHeader(AmazonHeaders.invokedFunctionARN)
66-
}
67-
68-
let traceID = headers.first(name: AmazonHeaders.traceID) ?? "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=0"
48+
guard let deadline = headers.first(name: AmazonHeaders.deadline) else {
49+
throw LambdaRuntimeError.invocationHeadMissingDeadlineInMillisSinceEpoch
50+
}
51+
guard let unixTimeInMilliseconds = Int64(deadline) else {
52+
throw LambdaRuntimeError.responseHeadInvalidDeadlineValue
53+
}
6954

70-
self.init(
71-
requestID: requestID,
72-
deadlineInMillisSinceEpoch: unixTimeInMilliseconds,
73-
invokedFunctionARN: invokedFunctionARN,
74-
traceID: traceID,
75-
clientContext: headers["Lambda-Runtime-Client-Context"].first,
76-
cognitoIdentity: headers["Lambda-Runtime-Cognito-Identity"].first
77-
)
78-
}
79-
}
55+
guard let invokedFunctionARN = headers.first(name: AmazonHeaders.invokedFunctionARN) else {
56+
throw LambdaRuntimeError.invocationHeadMissingFunctionARN
57+
}
8058

81-
struct ErrorResponse: Hashable, Codable {
82-
var errorType: String
83-
var errorMessage: String
84-
}
59+
let traceID = headers.first(name: AmazonHeaders.traceID) ?? "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=0"
8560

86-
extension ErrorResponse {
87-
internal func toJSONBytes() -> [UInt8] {
88-
var bytes = [UInt8]()
89-
bytes.append(UInt8(ascii: "{"))
90-
bytes.append(contentsOf: #""errorType":"#.utf8)
91-
self.errorType.encodeAsJSONString(into: &bytes)
92-
bytes.append(contentsOf: #","errorMessage":"#.utf8)
93-
self.errorMessage.encodeAsJSONString(into: &bytes)
94-
bytes.append(UInt8(ascii: "}"))
95-
return bytes
61+
self.init(
62+
requestID: requestID,
63+
deadlineInMillisSinceEpoch: unixTimeInMilliseconds,
64+
invokedFunctionARN: invokedFunctionARN,
65+
traceID: traceID,
66+
clientContext: headers["Lambda-Runtime-Client-Context"].first,
67+
cognitoIdentity: headers["Lambda-Runtime-Cognito-Identity"].first
68+
)
69+
}
9670
}
9771
}
Original file line numberDiff line numberDiff line change
@@ -1,88 +1,76 @@
1-
//===----------------------------------------------------------------------===//
2-
//
3-
// This source file is part of the SwiftAWSLambdaRuntime open source project
4-
//
5-
// Copyright (c) 2021 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-
1+
@_spi(Lambda) import LambdaRuntimeCore
152
import NIOCore
163

17-
struct ControlPlaneRequestEncoder: _EmittingChannelHandler {
18-
typealias OutboundOut = ByteBuffer
4+
@_spi(Lambda)
5+
extension AWSLambda {
6+
public struct RequestEncoder: ControlPlaneRequestEncoder {
7+
private var host: String
8+
private var byteBuffer: ByteBuffer!
199

20-
private var host: String
21-
private var byteBuffer: ByteBuffer!
10+
public init(host: String) {
11+
self.host = host
12+
}
2213

23-
init(host: String) {
24-
self.host = host
25-
}
14+
@_spi(Lambda) public mutating func writeRequest(_ request: ControlPlaneRequest, context: ChannelHandlerContext, promise: EventLoopPromise<Void>?) {
15+
self.byteBuffer.clear(minimumCapacity: self.byteBuffer.storageCapacity)
2616

27-
mutating func writeRequest(_ request: ControlPlaneRequest, context: ChannelHandlerContext, promise: EventLoopPromise<Void>?) {
28-
self.byteBuffer.clear(minimumCapacity: self.byteBuffer.storageCapacity)
29-
30-
switch request {
31-
case .next:
32-
self.byteBuffer.writeString(.nextInvocationRequestLine)
33-
self.byteBuffer.writeHostHeader(host: self.host)
34-
self.byteBuffer.writeString(.userAgentHeader)
35-
self.byteBuffer.writeString(.CRLF) // end of head
36-
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
37-
context.flush()
38-
39-
case .invocationResponse(let requestID, let payload):
40-
let contentLength = payload?.readableBytes ?? 0
41-
self.byteBuffer.writeInvocationResultRequestLine(requestID)
42-
self.byteBuffer.writeHostHeader(host: self.host)
43-
self.byteBuffer.writeString(.userAgentHeader)
44-
self.byteBuffer.writeContentLengthHeader(length: contentLength)
45-
self.byteBuffer.writeString(.CRLF) // end of head
46-
if let payload = payload, contentLength > 0 {
47-
context.write(self.wrapOutboundOut(self.byteBuffer), promise: nil)
48-
context.write(self.wrapOutboundOut(payload), promise: promise)
49-
} else {
17+
switch request {
18+
case .next:
19+
self.byteBuffer.writeString(.nextInvocationRequestLine)
20+
self.byteBuffer.writeHostHeader(host: self.host)
21+
self.byteBuffer.writeString(.userAgentHeader)
22+
self.byteBuffer.writeString(.CRLF) // end of head
23+
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
24+
context.flush()
25+
26+
case .invocationResponse(let requestID, let payload):
27+
let contentLength = payload?.readableBytes ?? 0
28+
self.byteBuffer.writeInvocationResultRequestLine(requestID)
29+
self.byteBuffer.writeHostHeader(host: self.host)
30+
self.byteBuffer.writeString(.userAgentHeader)
31+
self.byteBuffer.writeContentLengthHeader(length: contentLength)
32+
self.byteBuffer.writeString(.CRLF) // end of head
33+
if let payload = payload, contentLength > 0 {
34+
context.write(self.wrapOutboundOut(self.byteBuffer), promise: nil)
35+
context.write(self.wrapOutboundOut(payload), promise: promise)
36+
} else {
37+
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
38+
}
39+
context.flush()
40+
41+
case .invocationError(let requestID, let errorMessage):
42+
let payload = errorMessage.toJSONBytes()
43+
self.byteBuffer.writeInvocationErrorRequestLine(requestID)
44+
self.byteBuffer.writeContentLengthHeader(length: payload.count)
45+
self.byteBuffer.writeHostHeader(host: self.host)
46+
self.byteBuffer.writeString(.userAgentHeader)
47+
self.byteBuffer.writeString(.unhandledErrorHeader)
48+
self.byteBuffer.writeString(.CRLF) // end of head
49+
self.byteBuffer.writeBytes(payload)
50+
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
51+
context.flush()
52+
53+
case .initializationError(let errorMessage):
54+
let payload = errorMessage.toJSONBytes()
55+
self.byteBuffer.writeString(.runtimeInitErrorRequestLine)
56+
self.byteBuffer.writeContentLengthHeader(length: payload.count)
57+
self.byteBuffer.writeHostHeader(host: self.host)
58+
self.byteBuffer.writeString(.userAgentHeader)
59+
self.byteBuffer.writeString(.unhandledErrorHeader)
60+
self.byteBuffer.writeString(.CRLF) // end of head
61+
self.byteBuffer.writeBytes(payload)
5062
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
63+
context.flush()
5164
}
52-
context.flush()
53-
54-
case .invocationError(let requestID, let errorMessage):
55-
let payload = errorMessage.toJSONBytes()
56-
self.byteBuffer.writeInvocationErrorRequestLine(requestID)
57-
self.byteBuffer.writeContentLengthHeader(length: payload.count)
58-
self.byteBuffer.writeHostHeader(host: self.host)
59-
self.byteBuffer.writeString(.userAgentHeader)
60-
self.byteBuffer.writeString(.unhandledErrorHeader)
61-
self.byteBuffer.writeString(.CRLF) // end of head
62-
self.byteBuffer.writeBytes(payload)
63-
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
64-
context.flush()
65-
66-
case .initializationError(let errorMessage):
67-
let payload = errorMessage.toJSONBytes()
68-
self.byteBuffer.writeString(.runtimeInitErrorRequestLine)
69-
self.byteBuffer.writeContentLengthHeader(length: payload.count)
70-
self.byteBuffer.writeHostHeader(host: self.host)
71-
self.byteBuffer.writeString(.userAgentHeader)
72-
self.byteBuffer.writeString(.unhandledErrorHeader)
73-
self.byteBuffer.writeString(.CRLF) // end of head
74-
self.byteBuffer.writeBytes(payload)
75-
context.write(self.wrapOutboundOut(self.byteBuffer), promise: promise)
76-
context.flush()
7765
}
78-
}
7966

80-
mutating func writerAdded(context: ChannelHandlerContext) {
81-
self.byteBuffer = context.channel.allocator.buffer(capacity: 256)
82-
}
67+
public mutating func writerAdded(context: ChannelHandlerContext) {
68+
self.byteBuffer = context.channel.allocator.buffer(capacity: 256)
69+
}
8370

84-
mutating func writerRemoved(context: ChannelHandlerContext) {
85-
self.byteBuffer = nil
71+
public mutating func writerRemoved(context: ChannelHandlerContext) {
72+
self.byteBuffer = nil
73+
}
8674
}
8775
}
8876

@@ -124,3 +112,4 @@ extension ByteBuffer {
124112
self.writeString(.CRLF)
125113
}
126114
}
115+

0 commit comments

Comments
 (0)