diff --git a/Package.swift b/Package.swift index a94ba22b..a3c747eb 100644 --- a/Package.swift +++ b/Package.swift @@ -2,9 +2,15 @@ import PackageDescription +let defaultSwiftSettings: [SwiftSetting] = + [ + .enableExperimentalFeature( + "AvailabilityMacro=LambdaSwift 2.0:macOS 15.0" + ) + ] + let package = Package( name: "swift-aws-lambda-runtime", - platforms: [.macOS(.v15)], products: [ .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // plugin to package the lambda, creating an archive that can be uploaded to AWS @@ -43,7 +49,8 @@ let package = Package( package: "swift-service-lifecycle", condition: .when(traits: ["ServiceLifecycleSupport"]) ), - ] + ], + swiftSettings: defaultSwiftSettings ), .plugin( name: "AWSLambdaPackager", @@ -67,8 +74,10 @@ let package = Package( .byName(name: "AWSLambdaRuntime"), .product(name: "NIOTestUtils", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), - ] + ], + swiftSettings: defaultSwiftSettings ), + // for perf testing .executableTarget( name: "MockServer", @@ -77,7 +86,8 @@ let package = Package( .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOPosix", package: "swift-nio"), - ] + ], + swiftSettings: defaultSwiftSettings ), ] ) diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index a10bb0b8..95e8c881 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -2,9 +2,17 @@ import PackageDescription +let defaultSwiftSettings: [SwiftSetting] = [ + .define("FoundationJSONSupport"), + .define("ServiceLifecycleSupport"), + .define("LocalServerSupport"), + .enableExperimentalFeature( + "AvailabilityMacro=LambdaSwift 2.0:macOS 15.0" + ), +] + let package = Package( name: "swift-aws-lambda-runtime", - platforms: [.macOS(.v15)], products: [ .library(name: "AWSLambdaRuntime", targets: ["AWSLambdaRuntime"]), // plugin to package the lambda, creating an archive that can be uploaded to AWS @@ -28,11 +36,7 @@ let package = Package( .product(name: "NIOPosix", package: "swift-nio"), .product(name: "ServiceLifecycle", package: "swift-service-lifecycle"), ], - swiftSettings: [ - .define("FoundationJSONSupport"), - .define("ServiceLifecycleSupport"), - .define("LocalServerSupport"), - ] + swiftSettings: defaultSwiftSettings ), .plugin( name: "AWSLambdaPackager", @@ -57,11 +61,7 @@ let package = Package( .product(name: "NIOTestUtils", package: "swift-nio"), .product(name: "NIOFoundationCompat", package: "swift-nio"), ], - swiftSettings: [ - .define("FoundationJSONSupport"), - .define("ServiceLifecycleSupport"), - .define("LocalServerSupport"), - ] + swiftSettings: defaultSwiftSettings ), // for perf testing .executableTarget( @@ -71,7 +71,8 @@ let package = Package( .product(name: "NIOHTTP1", package: "swift-nio"), .product(name: "NIOCore", package: "swift-nio"), .product(name: "NIOPosix", package: "swift-nio"), - ] + ], + swiftSettings: defaultSwiftSettings ), ] ) diff --git a/Sources/AWSLambdaRuntime/ControlPlaneRequest.swift b/Sources/AWSLambdaRuntime/ControlPlaneRequest.swift index 233d7aef..9fa933f1 100644 --- a/Sources/AWSLambdaRuntime/ControlPlaneRequest.swift +++ b/Sources/AWSLambdaRuntime/ControlPlaneRequest.swift @@ -15,6 +15,7 @@ import NIOCore import NIOHTTP1 +@available(LambdaSwift 2.0, *) enum ControlPlaneRequest: Hashable { case next case invocationResponse(String, ByteBuffer?) @@ -22,6 +23,7 @@ enum ControlPlaneRequest: Hashable { case initializationError(ErrorResponse) } +@available(LambdaSwift 2.0, *) enum ControlPlaneResponse: Hashable { case next(InvocationMetadata, ByteBuffer) case accepted @@ -29,6 +31,7 @@ enum ControlPlaneResponse: Hashable { } @usableFromInline +@available(LambdaSwift 2.0, *) package struct InvocationMetadata: Hashable, Sendable { @usableFromInline package let requestID: String diff --git a/Sources/AWSLambdaRuntime/ControlPlaneRequestEncoder.swift b/Sources/AWSLambdaRuntime/ControlPlaneRequestEncoder.swift index 84f0cf0e..6934b064 100644 --- a/Sources/AWSLambdaRuntime/ControlPlaneRequestEncoder.swift +++ b/Sources/AWSLambdaRuntime/ControlPlaneRequestEncoder.swift @@ -14,6 +14,7 @@ import NIOCore +@available(LambdaSwift 2.0, *) struct ControlPlaneRequestEncoder: _EmittingChannelHandler { typealias OutboundOut = ByteBuffer diff --git a/Sources/AWSLambdaRuntime/FoundationSupport/Context+Foundation.swift b/Sources/AWSLambdaRuntime/FoundationSupport/Context+Foundation.swift index fad7dcec..1dd6584c 100644 --- a/Sources/AWSLambdaRuntime/FoundationSupport/Context+Foundation.swift +++ b/Sources/AWSLambdaRuntime/FoundationSupport/Context+Foundation.swift @@ -19,6 +19,7 @@ import FoundationEssentials import struct Foundation.Date #endif +@available(LambdaSwift 2.0, *) extension LambdaContext { /// Returns the deadline as a Date for the Lambda function execution. /// I'm not sure how usefull it is to have this as a Date, with only seconds precision, diff --git a/Sources/AWSLambdaRuntime/FoundationSupport/Lambda+JSON.swift b/Sources/AWSLambdaRuntime/FoundationSupport/Lambda+JSON.swift index 9b47bd7d..4d74d48e 100644 --- a/Sources/AWSLambdaRuntime/FoundationSupport/Lambda+JSON.swift +++ b/Sources/AWSLambdaRuntime/FoundationSupport/Lambda+JSON.swift @@ -59,6 +59,7 @@ public struct LambdaJSONOutputEncoder: LambdaOutputEncoder { } } +@available(LambdaSwift 2.0, *) extension LambdaCodableAdapter { /// Initializes an instance given an encoder, decoder, and a handler with a non-`Void` output. /// - Parameters: @@ -84,6 +85,7 @@ extension LambdaCodableAdapter { } } +@available(LambdaSwift 2.0, *) extension LambdaRuntime { /// Initialize an instance with a `LambdaHandler` defined in the form of a closure **with a non-`Void` return type**. /// - Parameters: diff --git a/Sources/AWSLambdaRuntime/Lambda+Codable.swift b/Sources/AWSLambdaRuntime/Lambda+Codable.swift index abc8728b..bf88b67c 100644 --- a/Sources/AWSLambdaRuntime/Lambda+Codable.swift +++ b/Sources/AWSLambdaRuntime/Lambda+Codable.swift @@ -48,6 +48,7 @@ public struct VoidEncoder: LambdaOutputEncoder { } /// Adapts a ``LambdaHandler`` conforming handler to conform to ``LambdaWithBackgroundProcessingHandler``. +@available(LambdaSwift 2.0, *) public struct LambdaHandlerAdapter< Event: Decodable, Output, @@ -80,6 +81,7 @@ public struct LambdaHandlerAdapter< } /// Adapts a ``LambdaWithBackgroundProcessingHandler`` conforming handler to conform to ``StreamingLambdaHandler``. +@available(LambdaSwift 2.0, *) public struct LambdaCodableAdapter< Handler: LambdaWithBackgroundProcessingHandler, Event: Decodable, diff --git a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift index 322bce1a..45468c97 100644 --- a/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift +++ b/Sources/AWSLambdaRuntime/Lambda+LocalServer.swift @@ -37,6 +37,7 @@ import Synchronization // ) // } // } +@available(LambdaSwift 2.0, *) extension Lambda { /// Execute code in the context of a mock Lambda server. /// @@ -84,6 +85,7 @@ extension Lambda { /// 1. POST /invoke - the client posts the event to the lambda function /// /// 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. +@available(LambdaSwift 2.0, *) internal struct LambdaHTTPServer { private let invocationEndpoint: String diff --git a/Sources/AWSLambdaRuntime/Lambda.swift b/Sources/AWSLambdaRuntime/Lambda.swift index f6223cb5..d53efade 100644 --- a/Sources/AWSLambdaRuntime/Lambda.swift +++ b/Sources/AWSLambdaRuntime/Lambda.swift @@ -29,6 +29,7 @@ import ucrt #error("Unsupported platform") #endif +@available(LambdaSwift 2.0, *) public enum Lambda { @inlinable package static func runLoop( @@ -98,6 +99,7 @@ public enum Lambda { // MARK: - Public API +@available(LambdaSwift 2.0, *) extension Lambda { /// Utility to access/read environment variables public static func env(_ name: String) -> String? { diff --git a/Sources/AWSLambdaRuntime/LambdaClock.swift b/Sources/AWSLambdaRuntime/LambdaClock.swift index 53b641cc..5fe65e75 100644 --- a/Sources/AWSLambdaRuntime/LambdaClock.swift +++ b/Sources/AWSLambdaRuntime/LambdaClock.swift @@ -51,6 +51,7 @@ import ucrt /// The Lambda execution environment uses UTC as a timezone, /// `LambdaClock` operates in UTC and does not account for time zones. /// see: TZ in https://docs.aws.amazon.com/lambda/latest/dg/configuration-envvars.html +@available(LambdaSwift 2.0, *) public struct LambdaClock: Clock { public typealias Duration = Swift.Duration diff --git a/Sources/AWSLambdaRuntime/LambdaContext.swift b/Sources/AWSLambdaRuntime/LambdaContext.swift index 0407f9e6..df0166d2 100644 --- a/Sources/AWSLambdaRuntime/LambdaContext.swift +++ b/Sources/AWSLambdaRuntime/LambdaContext.swift @@ -83,6 +83,7 @@ public struct ClientContext: Codable, Sendable { /// Lambda runtime context. /// The Lambda runtime generates and passes the `LambdaContext` to the Lambda handler as an argument. +@available(LambdaSwift 2.0, *) public struct LambdaContext: CustomDebugStringConvertible, Sendable { final class _Storage: Sendable { let requestID: String diff --git a/Sources/AWSLambdaRuntime/LambdaHandlers.swift b/Sources/AWSLambdaRuntime/LambdaHandlers.swift index f79c69b5..4b42d0d7 100644 --- a/Sources/AWSLambdaRuntime/LambdaHandlers.swift +++ b/Sources/AWSLambdaRuntime/LambdaHandlers.swift @@ -21,6 +21,7 @@ import NIOCore /// Background work can also be executed after returning the response. After closing the response stream by calling /// ``LambdaResponseStreamWriter/finish()`` or ``LambdaResponseStreamWriter/writeAndFinish(_:)``, /// the ``handle(_:responseWriter:context:)`` function is free to execute any background work. +@available(LambdaSwift 2.0, *) public protocol StreamingLambdaHandler: _Lambda_SendableMetatype { /// The handler function -- implement the business logic of the Lambda function here. /// - Parameters: @@ -66,6 +67,7 @@ public protocol LambdaResponseStreamWriter { /// /// - note: This handler protocol does not support response streaming because the output has to be encoded prior to it being sent, e.g. it is not possible to encode a partial/incomplete JSON string. /// This protocol also does not support the execution of background work after the response has been returned -- the ``LambdaWithBackgroundProcessingHandler`` protocol caters for such use-cases. +@available(LambdaSwift 2.0, *) public protocol LambdaHandler { /// Generic input type. /// The body of the request sent to Lambda will be decoded into this type for the handler to consume. @@ -88,6 +90,7 @@ public protocol LambdaHandler { /// ``LambdaResponseWriter``that is passed in as an argument, meaning that the /// ``LambdaWithBackgroundProcessingHandler/handle(_:outputWriter:context:)`` function is then /// free to implement any background work after the result has been sent to the AWS Lambda control plane. +@available(LambdaSwift 2.0, *) public protocol LambdaWithBackgroundProcessingHandler { /// Generic input type. /// The body of the request sent to Lambda will be decoded into this type for the handler to consume. @@ -123,6 +126,7 @@ public protocol LambdaResponseWriter { /// A ``StreamingLambdaHandler`` conforming handler object that can be constructed with a closure. /// Allows for a handler to be defined in a clean manner, leveraging Swift's trailing closure syntax. +@available(LambdaSwift 2.0, *) public struct StreamingClosureHandler: StreamingLambdaHandler { let body: @Sendable (ByteBuffer, LambdaResponseStreamWriter, LambdaContext) async throws -> Void @@ -151,6 +155,7 @@ public struct StreamingClosureHandler: StreamingLambdaHandler { /// A ``LambdaHandler`` conforming handler object that can be constructed with a closure. /// Allows for a handler to be defined in a clean manner, leveraging Swift's trailing closure syntax. +@available(LambdaSwift 2.0, *) public struct ClosureHandler: LambdaHandler { let body: (Event, LambdaContext) async throws -> Output @@ -175,6 +180,7 @@ public struct ClosureHandler: LambdaHandler { } } +@available(LambdaSwift 2.0, *) extension LambdaRuntime { /// Initialize an instance with a ``StreamingLambdaHandler`` in the form of a closure. /// - Parameter diff --git a/Sources/AWSLambdaRuntime/LambdaResponseStreamWriter+Headers.swift b/Sources/AWSLambdaRuntime/LambdaResponseStreamWriter+Headers.swift index 55ed3531..b1796fcd 100644 --- a/Sources/AWSLambdaRuntime/LambdaResponseStreamWriter+Headers.swift +++ b/Sources/AWSLambdaRuntime/LambdaResponseStreamWriter+Headers.swift @@ -87,6 +87,7 @@ extension LambdaResponseStreamWriter { } } +@available(LambdaSwift 2.0, *) extension LambdaResponseStreamWriter { /// Writes the HTTP status code and headers to the response stream. /// diff --git a/Sources/AWSLambdaRuntime/LambdaRuntime+ServiceLifecycle.swift b/Sources/AWSLambdaRuntime/LambdaRuntime+ServiceLifecycle.swift index 1b05b1c2..7489e8a3 100644 --- a/Sources/AWSLambdaRuntime/LambdaRuntime+ServiceLifecycle.swift +++ b/Sources/AWSLambdaRuntime/LambdaRuntime+ServiceLifecycle.swift @@ -15,6 +15,7 @@ #if ServiceLifecycleSupport import ServiceLifecycle +@available(LambdaSwift 2.0, *) extension LambdaRuntime: Service { public func run() async throws { try await cancelWhenGracefulShutdown { diff --git a/Sources/AWSLambdaRuntime/LambdaRuntime.swift b/Sources/AWSLambdaRuntime/LambdaRuntime.swift index a639ac31..906b1f1b 100644 --- a/Sources/AWSLambdaRuntime/LambdaRuntime.swift +++ b/Sources/AWSLambdaRuntime/LambdaRuntime.swift @@ -24,8 +24,10 @@ import Foundation // This is our guardian to ensure only one LambdaRuntime is running at the time // We use an Atomic here to ensure thread safety +@available(LambdaSwift 2.0, *) private let _isRunning = Atomic(false) +@available(LambdaSwift 2.0, *) public final class LambdaRuntime: Sendable where Handler: StreamingLambdaHandler { @usableFromInline /// we protect the handler behind a Mutex to ensure that we only ever have one copy of it diff --git a/Sources/AWSLambdaRuntime/LambdaRuntimeClient+ChannelHandler.swift b/Sources/AWSLambdaRuntime/LambdaRuntimeClient+ChannelHandler.swift index e69cb032..2b66ee87 100644 --- a/Sources/AWSLambdaRuntime/LambdaRuntimeClient+ChannelHandler.swift +++ b/Sources/AWSLambdaRuntime/LambdaRuntimeClient+ChannelHandler.swift @@ -22,6 +22,7 @@ internal protocol LambdaChannelHandlerDelegate { func connectionErrorHappened(_ error: any Error, channel: any Channel) } +@available(LambdaSwift 2.0, *) internal final class LambdaChannelHandler { let nextInvocationPath = Consts.invocationURLPrefix + Consts.getNextInvocationURLSuffix @@ -359,6 +360,7 @@ internal final class LambdaChannelHandler (client: EmbeddedChannel, server: EmbeddedChannel) { let client = EmbeddedChannel(handler: ControlPlaneRequestEncoderHandler(host: self.host)) let server = EmbeddedChannel(handlers: [ @@ -38,6 +39,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testNextRequest() throws { let (client, server) = createChannels() defer { @@ -58,6 +60,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testPostInvocationSuccessWithoutBody() throws { let (client, server) = createChannels() defer { @@ -80,6 +83,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testPostInvocationSuccessWithBody() throws { let (client, server) = createChannels() defer { @@ -105,6 +109,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testPostInvocationErrorWithBody() throws { let (client, server) = createChannels() defer { @@ -133,6 +138,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testPostStartupError() throws { let (client, server) = createChannels() defer { @@ -159,6 +165,7 @@ struct ControlPlaneRequestEncoderTests { } @Test + @available(LambdaSwift 2.0, *) func testMultipleNextAndResponseSuccessRequests() throws { let (client, server) = createChannels() defer { @@ -183,6 +190,7 @@ struct ControlPlaneRequestEncoderTests { } } + @available(LambdaSwift 2.0, *) func sendRequest( _ request: ControlPlaneRequest, client: EmbeddedChannel, @@ -196,6 +204,7 @@ struct ControlPlaneRequestEncoderTests { } } +@available(LambdaSwift 2.0, *) private final class ControlPlaneRequestEncoderHandler: ChannelOutboundHandler { typealias OutboundIn = ControlPlaneRequest typealias OutboundOut = ByteBuffer diff --git a/Tests/AWSLambdaRuntimeTests/InvocationTests.swift b/Tests/AWSLambdaRuntimeTests/InvocationTests.swift index ea4eef1f..6d1d7afa 100644 --- a/Tests/AWSLambdaRuntimeTests/InvocationTests.swift +++ b/Tests/AWSLambdaRuntimeTests/InvocationTests.swift @@ -26,6 +26,7 @@ import Foundation @Suite struct InvocationTest { @Test + @available(LambdaSwift 2.0, *) func testInvocationTraceID() throws { let headers = HTTPHeaders([ (AmazonHeaders.requestID, "test"), diff --git a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTests.swift b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTests.swift index 8a78ee8a..2027616b 100644 --- a/Tests/AWSLambdaRuntimeTests/Lambda+CodableTests.swift +++ b/Tests/AWSLambdaRuntimeTests/Lambda+CodableTests.swift @@ -46,6 +46,7 @@ struct JSONTests { } @Test + @available(LambdaSwift 2.0, *) func testJSONHandlerWithOutput() async { let jsonEncoder = JSONEncoder() let jsonDecoder = JSONDecoder() diff --git a/Tests/AWSLambdaRuntimeTests/LambdaClockTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaClockTests.swift index b1d6f974..affd323a 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaClockTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaClockTests.swift @@ -26,6 +26,7 @@ import Foundation struct LambdaClockTests { @Test("Clock provides current time") + @available(LambdaSwift 2.0, *) func clockProvidesCurrentTime() { let clock = LambdaClock() let now = clock.now @@ -36,6 +37,7 @@ struct LambdaClockTests { } @Test("Instant can be advanced by duration") + @available(LambdaSwift 2.0, *) func instantCanBeAdvancedByDuration() { let clock = LambdaClock() let start = clock.now @@ -45,6 +47,7 @@ struct LambdaClockTests { } @Test("Duration calculation between instants") + @available(LambdaSwift 2.0, *) func durationCalculationBetweenInstants() { let clock = LambdaClock() let start = clock.now @@ -55,6 +58,7 @@ struct LambdaClockTests { } @Test("Instant comparison works correctly") + @available(LambdaSwift 2.0, *) func instantComparisonWorksCorrectly() { let clock = LambdaClock() let earlier = clock.now @@ -65,12 +69,14 @@ struct LambdaClockTests { } @Test("Clock minimum resolution is milliseconds") + @available(LambdaSwift 2.0, *) func clockMinimumResolutionIsMilliseconds() { let clock = LambdaClock() #expect(clock.minimumResolution == .milliseconds(1)) } @Test("Sleep until deadline works") + @available(LambdaSwift 2.0, *) func sleepUntilDeadlineWorks() async throws { let clock = LambdaClock() let start = clock.now @@ -87,6 +93,7 @@ struct LambdaClockTests { } @Test("Sleep with past deadline returns immediately") + @available(LambdaSwift 2.0, *) func sleepWithPastDeadlineReturnsImmediately() async throws { let clock = LambdaClock() let now = clock.now @@ -102,6 +109,7 @@ struct LambdaClockTests { } @Test("Duration to future instant returns negative duration") + @available(LambdaSwift 2.0, *) func durationToFutureInstantReturnsNegativeDuration() { let clock = LambdaClock() let futureDeadline = clock.now.advanced(by: .seconds(30)) @@ -116,6 +124,7 @@ struct LambdaClockTests { } @Test("LambdaClock now matches Foundation Date within tolerance") + @available(LambdaSwift 2.0, *) func lambdaClockNowMatchesFoundationDate() { let clock = LambdaClock() @@ -137,6 +146,7 @@ struct LambdaClockTests { ) } @Test("Instant renders as string with an epoch number") + @available(LambdaSwift 2.0, *) func instantRendersAsStringWithEpochNumber() { let clock = LambdaClock() let instant = clock.now diff --git a/Tests/AWSLambdaRuntimeTests/LambdaContextTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaContextTests.swift index 827105da..7a53c1ef 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaContextTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaContextTests.swift @@ -114,6 +114,7 @@ struct LambdaContextTests { } @Test("getRemainingTime returns positive duration for future deadline") + @available(LambdaSwift 2.0, *) func getRemainingTimeReturnsPositiveDurationForFutureDeadline() { // Create context with deadline 30 seconds in the future diff --git a/Tests/AWSLambdaRuntimeTests/LambdaResponseStreamWriter+HeadersTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaResponseStreamWriter+HeadersTests.swift index 7fc679fd..bcd38894 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaResponseStreamWriter+HeadersTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaResponseStreamWriter+HeadersTests.swift @@ -27,6 +27,7 @@ import Foundation struct LambdaResponseStreamWriterHeadersTests { @Test("Write status and headers with minimal response (status code only)") + @available(LambdaSwift 2.0, *) func testWriteStatusAndHeadersMinimal() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) @@ -43,6 +44,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Write status and headers with full response (all fields populated)") + @available(LambdaSwift 2.0, *) func testWriteStatusAndHeadersFull() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -79,6 +81,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Write status and headers with custom encoder") + @available(LambdaSwift 2.0, *) func testWriteStatusAndHeadersWithCustomEncoder() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -106,6 +109,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Write status and headers with only headers (no multiValueHeaders)") + @available(LambdaSwift 2.0, *) func testWriteStatusAndHeadersOnlyHeaders() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -131,6 +135,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Write status and headers with only multiValueHeaders (no headers)") + @available(LambdaSwift 2.0, *) func testWriteStatusAndHeadersOnlyMultiValueHeaders() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -161,6 +166,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Verify JSON serialization format matches expected structure") + @available(LambdaSwift 2.0, *) func testJSONSerializationFormat() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -198,6 +204,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Verify buffer contains both JSON and null byte separator") + @available(LambdaSwift 2.0, *) func testBufferContainsJsonAndSeparator() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) @@ -218,6 +225,7 @@ struct LambdaResponseStreamWriterHeadersTests { // MARK: - Error Handling Tests @Test("JSON serialization error propagation") + @available(LambdaSwift 2.0, *) func testJSONSerializationErrorPropagation() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) @@ -235,6 +243,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Write method error propagation") + @available(LambdaSwift 2.0, *) func testWriteMethodErrorPropagation() async throws { let writer = FailingMockLambdaResponseStreamWriter(failOnWriteCall: 1) // Fail on first write let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) @@ -251,6 +260,7 @@ struct LambdaResponseStreamWriterHeadersTests { // This test is no longer needed since we only have one write operation now @Test("Error types and messages are properly handled") + @available(LambdaSwift 2.0, *) func testErrorTypesAndMessages() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) @@ -271,6 +281,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("JSONEncoder error propagation with invalid data") + @available(LambdaSwift 2.0, *) func testJSONEncoderErrorPropagation() async throws { let writer = MockLambdaResponseStreamWriter() @@ -292,6 +303,7 @@ struct LambdaResponseStreamWriterHeadersTests { // MARK: - Integration Tests @Test("Integration: writeStatusAndHeaders with existing streaming methods") + @available(LambdaSwift 2.0, *) func testIntegrationWithExistingStreamingMethods() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse( @@ -336,6 +348,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Integration: multiple header writes work correctly") + @available(LambdaSwift 2.0, *) func testMultipleHeaderWrites() async throws { let writer = MockLambdaResponseStreamWriter() @@ -370,6 +383,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Integration: header write followed by body streaming compatibility") + @available(LambdaSwift 2.0, *) func testHeaderWriteFollowedByBodyStreaming() async throws { let writer = MockLambdaResponseStreamWriter() @@ -418,6 +432,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Integration: verify method works with different LambdaResponseStreamWriter implementations") + @available(LambdaSwift 2.0, *) func testWithDifferentWriterImplementations() async throws { // Test with basic mock implementation let basicWriter = MockLambdaResponseStreamWriter() @@ -441,6 +456,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Integration: complex scenario with headers, streaming, and finish") + @available(LambdaSwift 2.0, *) func testComplexIntegrationScenario() async throws { let writer = MockLambdaResponseStreamWriter() @@ -493,6 +509,7 @@ struct LambdaResponseStreamWriterHeadersTests { } @Test("Integration: verify compatibility with protocol requirements") + @available(LambdaSwift 2.0, *) func testProtocolCompatibility() async throws { let writer = MockLambdaResponseStreamWriter() let response = StreamingLambdaStatusAndHeadersResponse(statusCode: 200) diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRunLoopTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaRunLoopTests.swift index ec1c9265..0be96376 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRunLoopTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaRunLoopTests.swift @@ -26,6 +26,7 @@ import Foundation @Suite struct LambdaRunLoopTests { + @available(LambdaSwift 2.0, *) struct MockEchoHandler: StreamingLambdaHandler { func handle( _ event: ByteBuffer, @@ -37,6 +38,7 @@ struct LambdaRunLoopTests { } } + @available(LambdaSwift 2.0, *) struct FailingHandler: StreamingLambdaHandler { func handle( _ event: ByteBuffer, @@ -48,19 +50,19 @@ struct LambdaRunLoopTests { } } - let mockClient = MockLambdaClient() - let mockEchoHandler = MockEchoHandler() - let failingHandler = FailingHandler() - - @Test func testRunLoop() async throws { + @Test + @available(LambdaSwift 2.0, *) + func testRunLoop() async throws { + let mockClient = MockLambdaClient() + let mockEchoHandler = MockEchoHandler() let inputEvent = ByteBuffer(string: "Test Invocation Event") try await withThrowingTaskGroup(of: Void.self) { group in let logStore = CollectEverythingLogHandler.LogStore() group.addTask { try await Lambda.runLoop( - runtimeClient: self.mockClient, - handler: self.mockEchoHandler, + runtimeClient: mockClient, + handler: mockEchoHandler, logger: Logger( label: "RunLoopTest", factory: { _ in CollectEverythingLogHandler(logStore: logStore) } @@ -69,7 +71,7 @@ struct LambdaRunLoopTests { } let requestID = UUID().uuidString - let response = try await self.mockClient.invoke(event: inputEvent, requestID: requestID) + let response = try await mockClient.invoke(event: inputEvent, requestID: requestID) #expect(response == inputEvent) logStore.assertContainsLog("Test", ("aws-request-id", .exactMatch(requestID))) @@ -77,15 +79,19 @@ struct LambdaRunLoopTests { } } - @Test func testRunLoopError() async throws { + @Test + @available(LambdaSwift 2.0, *) + func testRunLoopError() async throws { + let mockClient = MockLambdaClient() + let failingHandler = FailingHandler() let inputEvent = ByteBuffer(string: "Test Invocation Event") await withThrowingTaskGroup(of: Void.self) { group in let logStore = CollectEverythingLogHandler.LogStore() group.addTask { try await Lambda.runLoop( - runtimeClient: self.mockClient, - handler: self.failingHandler, + runtimeClient: mockClient, + handler: failingHandler, logger: Logger( label: "RunLoopTest", factory: { _ in CollectEverythingLogHandler(logStore: logStore) } @@ -97,7 +103,7 @@ struct LambdaRunLoopTests { await #expect( throws: LambdaError.handlerError, performing: { - try await self.mockClient.invoke(event: inputEvent, requestID: requestID) + try await mockClient.invoke(event: inputEvent, requestID: requestID) } ) logStore.assertContainsLog("Test", ("aws-request-id", .exactMatch(requestID))) diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRuntime+ServiceLifeCycle.swift b/Tests/AWSLambdaRuntimeTests/LambdaRuntime+ServiceLifeCycle.swift index 7103ea8d..0954eb10 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRuntime+ServiceLifeCycle.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaRuntime+ServiceLifeCycle.swift @@ -21,6 +21,7 @@ import Logging @Suite struct LambdaRuntimeServiceLifecycleTests { @Test + @available(LambdaSwift 2.0, *) func testLambdaRuntimeGracefulShutdown() async throws { let runtime = LambdaRuntime { (event: String, context: LambdaContext) in diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift index f10026f9..a5b22471 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift @@ -33,6 +33,7 @@ struct LambdaRuntimeClientTests { }() @Test + @available(LambdaSwift 2.0, *) func testSimpleInvocations() async throws { struct HappyBehavior: LambdaServerBehavior { let requestId = UUID().uuidString @@ -127,6 +128,7 @@ struct LambdaRuntimeClientTests { } @Test + @available(LambdaSwift 2.0, *) func testStreamingResponseHeaders() async throws { let behavior = StreamingBehavior() @@ -153,6 +155,7 @@ struct LambdaRuntimeClientTests { } @Test + @available(LambdaSwift 2.0, *) func testStreamingResponseHeadersWithCustomStatus() async throws { let behavior = StreamingBehavior(customHeaders: true) @@ -188,6 +191,7 @@ struct LambdaRuntimeClientTests { } @Test + @available(LambdaSwift 2.0, *) func testRuntimeClientCancellation() async throws { struct HappyBehavior: LambdaServerBehavior { let requestId = UUID().uuidString @@ -285,6 +289,7 @@ struct LambdaRuntimeClientTests { "Server closing the connection when waiting for next invocation throws an error", arguments: [DisconnectBehavior(), DisconnectAfterSendingResponseBehavior()] as [any LambdaServerBehavior] ) + @available(LambdaSwift 2.0, *) func testChannelCloseFutureWithWaitingForNextInvocation(behavior: LambdaServerBehavior) async throws { try await withMockServer(behaviour: behavior) { port in let configuration = LambdaRuntimeClient.Configuration(ip: "127.0.0.1", port: port) diff --git a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeTests.swift b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeTests.swift index 6c159dc8..17c4cbf0 100644 --- a/Tests/AWSLambdaRuntimeTests/LambdaRuntimeTests.swift +++ b/Tests/AWSLambdaRuntimeTests/LambdaRuntimeTests.swift @@ -24,6 +24,7 @@ import Testing struct LambdaRuntimeTests { @Test("LambdaRuntime can only be run once") + @available(LambdaSwift 2.0, *) func testLambdaRuntimerunOnce() async throws { // First runtime @@ -68,6 +69,7 @@ struct LambdaRuntimeTests { } } @Test("run() must be cancellable") + @available(LambdaSwift 2.0, *) func testLambdaRuntimeCancellable() async throws { let logger = Logger(label: "LambdaRuntimeTests.RuntimeCancellable") @@ -116,6 +118,7 @@ struct LambdaRuntimeTests { } } +@available(LambdaSwift 2.0, *) struct MockHandler: StreamingLambdaHandler { mutating func handle( _ event: NIOCore.ByteBuffer, diff --git a/Tests/AWSLambdaRuntimeTests/MockLambdaClient.swift b/Tests/AWSLambdaRuntimeTests/MockLambdaClient.swift index bdc0ab7a..f35b08a0 100644 --- a/Tests/AWSLambdaRuntimeTests/MockLambdaClient.swift +++ b/Tests/AWSLambdaRuntimeTests/MockLambdaClient.swift @@ -22,6 +22,7 @@ import FoundationEssentials import Foundation #endif +@available(LambdaSwift 2.0, *) struct MockLambdaWriter: LambdaRuntimeClientResponseStreamWriter { var underlying: MockLambdaClient @@ -55,10 +56,13 @@ enum LambdaError: Error, Equatable { case handlerError } +@available(LambdaSwift 2.0, *) final actor MockLambdaClient: LambdaRuntimeClientProtocol { typealias Writer = MockLambdaWriter + @available(LambdaSwift 2.0, *) private struct StateMachine { + @available(LambdaSwift 2.0, *) private enum State { // The Lambda has just started, or an event has finished processing and the runtime is ready to receive more events. // Expecting a next() call by the runtime. @@ -79,6 +83,7 @@ final actor MockLambdaClient: LambdaRuntimeClientProtocol { // Queue incoming events if the runtime is busy handling an event. private var eventQueue = [Event]() + @available(LambdaSwift 2.0, *) enum InvokeAction { // The next endpoint is waiting for an event. Deliver this newly arrived event to it. case readyToProcess(_ eventArrivedHandler: CheckedContinuation) @@ -87,6 +92,7 @@ final actor MockLambdaClient: LambdaRuntimeClientProtocol { case wait } + @available(LambdaSwift 2.0, *) enum NextAction { // There is an event available to be processed. case readyToProcess(Invocation) @@ -97,6 +103,7 @@ final actor MockLambdaClient: LambdaRuntimeClientProtocol { case fail(LambdaError) } + @available(LambdaSwift 2.0, *) enum CancelNextAction { case none @@ -209,6 +216,7 @@ final actor MockLambdaClient: LambdaRuntimeClientProtocol { private var stateMachine = StateMachine() + @available(LambdaSwift 2.0, *) struct Event { let invocation: Invocation let eventProcessedHandler: CheckedContinuation diff --git a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift b/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift index d5ad8876..8429f7fd 100644 --- a/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift +++ b/Tests/AWSLambdaRuntimeTests/MockLambdaServer.swift @@ -184,10 +184,16 @@ final class HTTPHandler: ChannelInboundHandler { responseStatus = .ok responseBody = result let deadline = Date(timeIntervalSinceNow: 60).millisSinceEpoch + let traceID: String + if #available(macOS 15.0, *) { + traceID = "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1" + } else { + traceID = "Root=1-00000000-000000000000000000000000;Sampled=1" + } responseHeaders = [ (AmazonHeaders.requestID, requestId), (AmazonHeaders.invokedFunctionARN, "arn:aws:lambda:us-east-1:123456789012:function:custom-runtime"), - (AmazonHeaders.traceID, "Root=\(AmazonHeaders.generateXRayTraceID());Sampled=1"), + (AmazonHeaders.traceID, traceID), (AmazonHeaders.deadline, String(deadline)), ] case .failure(let error): diff --git a/Tests/AWSLambdaRuntimeTests/PoolTests.swift b/Tests/AWSLambdaRuntimeTests/PoolTests.swift index 15d54a73..8cbe8a2e 100644 --- a/Tests/AWSLambdaRuntimeTests/PoolTests.swift +++ b/Tests/AWSLambdaRuntimeTests/PoolTests.swift @@ -19,6 +19,7 @@ import Testing struct PoolTests { @Test + @available(LambdaSwift 2.0, *) func testBasicPushAndIteration() async throws { let pool = LambdaHTTPServer.Pool() @@ -37,6 +38,7 @@ struct PoolTests { } @Test + @available(LambdaSwift 2.0, *) func testPoolCancellation() async throws { let pool = LambdaHTTPServer.Pool() @@ -55,6 +57,7 @@ struct PoolTests { } @Test + @available(LambdaSwift 2.0, *) func testConcurrentPushAndIteration() async throws { let pool = LambdaHTTPServer.Pool() let iterations = 1000 @@ -90,6 +93,7 @@ struct PoolTests { } @Test + @available(LambdaSwift 2.0, *) func testPushToWaitingConsumer() async throws { let pool = LambdaHTTPServer.Pool() let expectedValue = "test value" @@ -113,6 +117,7 @@ struct PoolTests { } @Test + @available(LambdaSwift 2.0, *) func testStressTest() async throws { let pool = LambdaHTTPServer.Pool() let producerCount = 10 diff --git a/Tests/AWSLambdaRuntimeTests/UtilsTest.swift b/Tests/AWSLambdaRuntimeTests/UtilsTest.swift index 15a5fd27..e519ce0c 100644 --- a/Tests/AWSLambdaRuntimeTests/UtilsTest.swift +++ b/Tests/AWSLambdaRuntimeTests/UtilsTest.swift @@ -18,6 +18,7 @@ import Testing struct UtilsTest { @Test + @available(LambdaSwift 2.0, *) func testGenerateXRayTraceID() { // the time and identifier should be in hexadecimal digits let allowedCharacters = "0123456789abcdef"