Skip to content

Commit f7ad7c0

Browse files
committed
Merge branch 'main' into sebsto/fix_507
2 parents 9fb86a7 + b3fbee6 commit f7ad7c0

13 files changed

+446
-118
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
with:
2424
linux_5_9_enabled: false
2525
linux_5_10_enabled: false
26-
linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error"
26+
linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error"
2727
linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error"
2828

2929
integration-tests:

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ Package.resolved
1111
.serverless
1212
.vscode
1313
Makefile
14-
.devcontainer
14+
.devcontainer
15+
.amazonq

[email protected]

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ let package = Package(
5656
.byName(name: "AWSLambdaRuntime"),
5757
.product(name: "NIOTestUtils", package: "swift-nio"),
5858
.product(name: "NIOFoundationCompat", package: "swift-nio"),
59+
],
60+
swiftSettings: [
61+
.define("FoundationJSONSupport"),
62+
.define("ServiceLifecycleSupport"),
63+
.define("LocalServerSupport"),
5964
]
6065
),
6166
// for perf testing

Sources/AWSLambdaRuntime/Lambda+LocalServer.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the SwiftAWSLambdaRuntime open source project
44
//
5-
// Copyright (c) 2020 Apple Inc. and the SwiftAWSLambdaRuntime project authors
5+
// Copyright (c) 2025 Apple Inc. and the SwiftAWSLambdaRuntime project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -76,7 +76,7 @@ extension Lambda {
7676
/// 1. POST /invoke - the client posts the event to the lambda function
7777
///
7878
/// This server passes the data received from /invoke POST request to the lambda function (GET /next) and then forwards the response back to the client.
79-
private struct LambdaHTTPServer {
79+
internal struct LambdaHTTPServer {
8080
private let invocationEndpoint: String
8181

8282
private let invocationPool = Pool<LocalServerInvocation>()
@@ -426,7 +426,7 @@ private struct LambdaHTTPServer {
426426
/// A shared data structure to store the current invocation or response requests and the continuation objects.
427427
/// This data structure is shared between instances of the HTTPHandler
428428
/// (one instance to serve requests from the Lambda function and one instance to serve requests from the client invoking the lambda function).
429-
private final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
429+
internal final class Pool<T>: AsyncSequence, AsyncIteratorProtocol, Sendable where T: Sendable {
430430
typealias Element = T
431431

432432
enum State: ~Copyable {

Sources/AWSLambdaRuntime/LambdaHandlers.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import NIOCore
2020
/// Background work can also be executed after returning the response. After closing the response stream by calling
2121
/// ``LambdaResponseStreamWriter/finish()`` or ``LambdaResponseStreamWriter/writeAndFinish(_:)``,
2222
/// the ``handle(_:responseWriter:context:)`` function is free to execute any background work.
23-
public protocol StreamingLambdaHandler {
23+
public protocol StreamingLambdaHandler: _Lambda_SendableMetatype {
2424
/// The handler function -- implement the business logic of the Lambda function here.
2525
/// - Parameters:
2626
/// - event: The invocation's input data.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftAWSLambdaRuntime open source project
4+
//
5+
// Copyright (c) 2025 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+
#if compiler(>=6.2)
16+
@_documentation(visibility: internal)
17+
public typealias _Lambda_SendableMetatype = SendableMetatype
18+
#else
19+
@_documentation(visibility: internal)
20+
public typealias _Lambda_SendableMetatype = Any
21+
#endif

Tests/AWSLambdaRuntimeTests/ControlPlaneRequestEncoderTests.swift

Lines changed: 133 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,148 +15,184 @@
1515
import NIOCore
1616
import NIOEmbedded
1717
import NIOHTTP1
18-
import XCTest
18+
import Testing
1919

2020
@testable import AWSLambdaRuntime
2121

22-
final class ControlPlaneRequestEncoderTests: XCTestCase {
23-
let host = "192.168.0.1"
22+
#if canImport(FoundationEssentials)
23+
import FoundationEssentials
24+
#else
25+
import Foundation
26+
#endif
2427

25-
var client: EmbeddedChannel!
26-
var server: EmbeddedChannel!
28+
struct ControlPlaneRequestEncoderTests {
29+
let host = "192.168.0.1"
2730

28-
override func setUp() {
29-
self.client = EmbeddedChannel(handler: ControlPlaneRequestEncoderHandler(host: self.host))
30-
self.server = EmbeddedChannel(handlers: [
31+
func createChannels() -> (client: EmbeddedChannel, server: EmbeddedChannel) {
32+
let client = EmbeddedChannel(handler: ControlPlaneRequestEncoderHandler(host: self.host))
33+
let server = EmbeddedChannel(handlers: [
3134
ByteToMessageHandler(HTTPRequestDecoder(leftOverBytesStrategy: .dropBytes)),
3235
NIOHTTPServerRequestAggregator(maxContentLength: 1024 * 1024),
3336
])
37+
return (client, server)
3438
}
3539

36-
override func tearDown() {
37-
XCTAssertNoThrow(try self.client.finish(acceptAlreadyClosed: false))
38-
XCTAssertNoThrow(try self.server.finish(acceptAlreadyClosed: false))
39-
self.client = nil
40-
self.server = nil
41-
}
40+
@Test
41+
func testNextRequest() throws {
42+
let (client, server) = createChannels()
43+
defer {
44+
_ = try? client.finish(acceptAlreadyClosed: false)
45+
_ = try? server.finish(acceptAlreadyClosed: false)
46+
}
4247

43-
func testNextRequest() {
44-
var request: NIOHTTPServerRequestFull?
45-
XCTAssertNoThrow(request = try self.sendRequest(.next))
48+
let request = try sendRequest(.next, client: client, server: server)
4649

47-
XCTAssertEqual(request?.head.isKeepAlive, true)
48-
XCTAssertEqual(request?.head.method, .GET)
49-
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/next")
50-
XCTAssertEqual(request?.head.version, .http1_1)
51-
XCTAssertEqual(request?.head.headers["host"], [self.host])
52-
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
50+
#expect(request?.head.isKeepAlive == true)
51+
#expect(request?.head.method == .GET)
52+
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/next")
53+
#expect(request?.head.version == .http1_1)
54+
#expect(request?.head.headers["host"] == [self.host])
55+
#expect(request?.head.headers["user-agent"] == [.userAgent])
5356

54-
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
57+
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
5558
}
5659

57-
func testPostInvocationSuccessWithoutBody() {
60+
@Test
61+
func testPostInvocationSuccessWithoutBody() throws {
62+
let (client, server) = createChannels()
63+
defer {
64+
_ = try? client.finish(acceptAlreadyClosed: false)
65+
_ = try? server.finish(acceptAlreadyClosed: false)
66+
}
67+
5868
let requestID = UUID().uuidString
59-
var request: NIOHTTPServerRequestFull?
60-
XCTAssertNoThrow(request = try self.sendRequest(.invocationResponse(requestID, nil)))
61-
62-
XCTAssertEqual(request?.head.isKeepAlive, true)
63-
XCTAssertEqual(request?.head.method, .POST)
64-
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
65-
XCTAssertEqual(request?.head.version, .http1_1)
66-
XCTAssertEqual(request?.head.headers["host"], [self.host])
67-
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
68-
XCTAssertEqual(request?.head.headers["content-length"], ["0"])
69-
70-
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
69+
let request = try sendRequest(.invocationResponse(requestID, nil), client: client, server: server)
70+
71+
#expect(request?.head.isKeepAlive == true)
72+
#expect(request?.head.method == .POST)
73+
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
74+
#expect(request?.head.version == .http1_1)
75+
#expect(request?.head.headers["host"] == [self.host])
76+
#expect(request?.head.headers["user-agent"] == [.userAgent])
77+
#expect(request?.head.headers["content-length"] == ["0"])
78+
79+
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
7180
}
7281

73-
func testPostInvocationSuccessWithBody() {
82+
@Test
83+
func testPostInvocationSuccessWithBody() throws {
84+
let (client, server) = createChannels()
85+
defer {
86+
_ = try? client.finish(acceptAlreadyClosed: false)
87+
_ = try? server.finish(acceptAlreadyClosed: false)
88+
}
89+
7490
let requestID = UUID().uuidString
7591
let payload = ByteBuffer(string: "hello swift lambda!")
7692

77-
var request: NIOHTTPServerRequestFull?
78-
XCTAssertNoThrow(request = try self.sendRequest(.invocationResponse(requestID, payload)))
93+
let request = try sendRequest(.invocationResponse(requestID, payload), client: client, server: server)
7994

80-
XCTAssertEqual(request?.head.isKeepAlive, true)
81-
XCTAssertEqual(request?.head.method, .POST)
82-
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
83-
XCTAssertEqual(request?.head.version, .http1_1)
84-
XCTAssertEqual(request?.head.headers["host"], [self.host])
85-
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
86-
XCTAssertEqual(request?.head.headers["content-length"], ["\(payload.readableBytes)"])
87-
XCTAssertEqual(request?.body, payload)
95+
#expect(request?.head.isKeepAlive == true)
96+
#expect(request?.head.method == .POST)
97+
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
98+
#expect(request?.head.version == .http1_1)
99+
#expect(request?.head.headers["host"] == [self.host])
100+
#expect(request?.head.headers["user-agent"] == [.userAgent])
101+
#expect(request?.head.headers["content-length"] == ["\(payload.readableBytes)"])
102+
#expect(request?.body == payload)
88103

89-
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
104+
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
90105
}
91106

92-
func testPostInvocationErrorWithBody() {
107+
@Test
108+
func testPostInvocationErrorWithBody() throws {
109+
let (client, server) = createChannels()
110+
defer {
111+
_ = try? client.finish(acceptAlreadyClosed: false)
112+
_ = try? server.finish(acceptAlreadyClosed: false)
113+
}
114+
93115
let requestID = UUID().uuidString
94116
let error = ErrorResponse(errorType: "SomeError", errorMessage: "An error happened")
95-
var request: NIOHTTPServerRequestFull?
96-
XCTAssertNoThrow(request = try self.sendRequest(.invocationError(requestID, error)))
97-
98-
XCTAssertEqual(request?.head.isKeepAlive, true)
99-
XCTAssertEqual(request?.head.method, .POST)
100-
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/error")
101-
XCTAssertEqual(request?.head.version, .http1_1)
102-
XCTAssertEqual(request?.head.headers["host"], [self.host])
103-
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
104-
XCTAssertEqual(request?.head.headers["lambda-runtime-function-error-type"], ["Unhandled"])
117+
let request = try sendRequest(.invocationError(requestID, error), client: client, server: server)
118+
119+
#expect(request?.head.isKeepAlive == true)
120+
#expect(request?.head.method == .POST)
121+
#expect(request?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/error")
122+
#expect(request?.head.version == .http1_1)
123+
#expect(request?.head.headers["host"] == [self.host])
124+
#expect(request?.head.headers["user-agent"] == [.userAgent])
125+
#expect(request?.head.headers["lambda-runtime-function-error-type"] == ["Unhandled"])
105126
let expectedBody = #"{"errorType":"SomeError","errorMessage":"An error happened"}"#
106127

107-
XCTAssertEqual(request?.head.headers["content-length"], ["\(expectedBody.utf8.count)"])
108-
XCTAssertEqual(
109-
try request?.body?.getString(at: 0, length: XCTUnwrap(request?.body?.readableBytes)),
110-
expectedBody
111-
)
128+
#expect(request?.head.headers["content-length"] == ["\(expectedBody.utf8.count)"])
129+
let bodyString = request?.body?.getString(at: 0, length: request?.body?.readableBytes ?? 0)
130+
#expect(bodyString == expectedBody)
112131

113-
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
132+
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
114133
}
115134

116-
func testPostStartupError() {
135+
@Test
136+
func testPostStartupError() throws {
137+
let (client, server) = createChannels()
138+
defer {
139+
_ = try? client.finish(acceptAlreadyClosed: false)
140+
_ = try? server.finish(acceptAlreadyClosed: false)
141+
}
142+
117143
let error = ErrorResponse(errorType: "StartupError", errorMessage: "Urgh! Startup failed. 😨")
118-
var request: NIOHTTPServerRequestFull?
119-
XCTAssertNoThrow(request = try self.sendRequest(.initializationError(error)))
120-
121-
XCTAssertEqual(request?.head.isKeepAlive, true)
122-
XCTAssertEqual(request?.head.method, .POST)
123-
XCTAssertEqual(request?.head.uri, "/2018-06-01/runtime/init/error")
124-
XCTAssertEqual(request?.head.version, .http1_1)
125-
XCTAssertEqual(request?.head.headers["host"], [self.host])
126-
XCTAssertEqual(request?.head.headers["user-agent"], [.userAgent])
127-
XCTAssertEqual(request?.head.headers["lambda-runtime-function-error-type"], ["Unhandled"])
144+
let request = try sendRequest(.initializationError(error), client: client, server: server)
145+
146+
#expect(request?.head.isKeepAlive == true)
147+
#expect(request?.head.method == .POST)
148+
#expect(request?.head.uri == "/2018-06-01/runtime/init/error")
149+
#expect(request?.head.version == .http1_1)
150+
#expect(request?.head.headers["host"] == [self.host])
151+
#expect(request?.head.headers["user-agent"] == [.userAgent])
152+
#expect(request?.head.headers["lambda-runtime-function-error-type"] == ["Unhandled"])
128153
let expectedBody = #"{"errorType":"StartupError","errorMessage":"Urgh! Startup failed. 😨"}"#
129-
XCTAssertEqual(request?.head.headers["content-length"], ["\(expectedBody.utf8.count)"])
130-
XCTAssertEqual(
131-
try request?.body?.getString(at: 0, length: XCTUnwrap(request?.body?.readableBytes)),
132-
expectedBody
133-
)
154+
#expect(request?.head.headers["content-length"] == ["\(expectedBody.utf8.count)"])
155+
let bodyString = request?.body?.getString(at: 0, length: request?.body?.readableBytes ?? 0)
156+
#expect(bodyString == expectedBody)
134157

135-
XCTAssertNil(try self.server.readInbound(as: NIOHTTPServerRequestFull.self))
158+
#expect(try server.readInbound(as: NIOHTTPServerRequestFull.self) == nil)
136159
}
137160

138-
func testMultipleNextAndResponseSuccessRequests() {
161+
@Test
162+
func testMultipleNextAndResponseSuccessRequests() throws {
163+
let (client, server) = createChannels()
164+
defer {
165+
_ = try? client.finish(acceptAlreadyClosed: false)
166+
_ = try? server.finish(acceptAlreadyClosed: false)
167+
}
168+
139169
for _ in 0..<1000 {
140-
var nextRequest: NIOHTTPServerRequestFull?
141-
XCTAssertNoThrow(nextRequest = try self.sendRequest(.next))
142-
XCTAssertEqual(nextRequest?.head.method, .GET)
143-
XCTAssertEqual(nextRequest?.head.uri, "/2018-06-01/runtime/invocation/next")
170+
let nextRequest = try sendRequest(.next, client: client, server: server)
171+
#expect(nextRequest?.head.method == .GET)
172+
#expect(nextRequest?.head.uri == "/2018-06-01/runtime/invocation/next")
144173

145174
let requestID = UUID().uuidString
146175
let payload = ByteBuffer(string: "hello swift lambda!")
147-
var successRequest: NIOHTTPServerRequestFull?
148-
XCTAssertNoThrow(successRequest = try self.sendRequest(.invocationResponse(requestID, payload)))
149-
XCTAssertEqual(successRequest?.head.method, .POST)
150-
XCTAssertEqual(successRequest?.head.uri, "/2018-06-01/runtime/invocation/\(requestID)/response")
176+
let successRequest = try sendRequest(
177+
.invocationResponse(requestID, payload),
178+
client: client,
179+
server: server
180+
)
181+
#expect(successRequest?.head.method == .POST)
182+
#expect(successRequest?.head.uri == "/2018-06-01/runtime/invocation/\(requestID)/response")
151183
}
152184
}
153185

154-
func sendRequest(_ request: ControlPlaneRequest) throws -> NIOHTTPServerRequestFull? {
155-
try self.client.writeOutbound(request)
156-
while let part = try self.client.readOutbound(as: ByteBuffer.self) {
157-
XCTAssertNoThrow(try self.server.writeInbound(part))
186+
func sendRequest(
187+
_ request: ControlPlaneRequest,
188+
client: EmbeddedChannel,
189+
server: EmbeddedChannel
190+
) throws -> NIOHTTPServerRequestFull? {
191+
try client.writeOutbound(request)
192+
while let part = try client.readOutbound(as: ByteBuffer.self) {
193+
try server.writeInbound(part)
158194
}
159-
return try self.server.readInbound(as: NIOHTTPServerRequestFull.self)
195+
return try server.readInbound(as: NIOHTTPServerRequestFull.self)
160196
}
161197
}
162198

Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ struct LambdaRuntimeClientTests {
2626

2727
let logger = {
2828
var logger = Logger(label: "NewLambdaClientRuntimeTest")
29-
logger.logLevel = .trace
29+
// Uncomment the line below to enable trace-level logging for debugging purposes.
30+
// logger.logLevel = .trace
3031
return logger
3132
}()
3233

0 commit comments

Comments
 (0)