Skip to content

Commit 262c3b5

Browse files
authored
Revert streaming codable handler and provide it as an example, not an API (#549)
Revert streaming codable handler change and propose it as an example instead of an handler API. **Motivation:** I made a mistake when submitting this PR #532 It provides a Streaming+Codable handler that conveniently allows developers to write handlers with `Codable` events for streaming functions. This is a mistake for three reasons: - This is the only handler that assumes a Lamba Event structure as input. I added a minimal `FunctionUrlRequest` and `FunctionURLResponse` to avoid importing the AWS Lambda Events library. It is the first handler to be event-specific. I don't think the runtime should introduce event specific code. - The handler only works when Lambda functions are exposed through Function URLs. Streaming functions can also be invoke by API or CLI. - The handler hides `FunctionURLRequest` details (HTTP headers, query parameters, etc.) from developers Developers were unaware they were trading flexibility for convenience The lack of clear documentation about these limitations led to incorrect usage patterns and frustrated developers who needed full request control or were using other invocation methods. **Modifications:** - Removed the Streaming+Codable API from the library - Moved the Streaming+Codable code to an example - Added prominent warning section in the example README explaining the limitations - Clarified when to use Streaming+Codable vs ByteBuffer approaches - Added decision rule framework to help developers choose the right approach **Result:** The only API provided by the library to use Streaming Lambda functions is exposing the raw `ByteBuffer` as input, there is no more `Codable` handler for Streaming functions available in the API. I kept the `Streaming+Codable` code an example. After this change, developers have clear guidance on when to use each streaming approach: - Use streaming codable for Function URL + JSON payload + no request details needed - Use ByteBuffer StreamingLambdaHandler for full control, other invocation methods, or request metadata access This prevents misuse of the API and sets proper expectations about the handler's capabilities and limitations, leading to better developer experience and fewer integration issues.
1 parent af7e9db commit 262c3b5

File tree

10 files changed

+59
-159
lines changed

10 files changed

+59
-159
lines changed

.github/workflows/pull_request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ jobs:
3636
# We pass the list of examples here, but we can't pass an array as argument
3737
# Instead, we pass a String with a valid JSON array.
3838
# The workaround is mentioned here https://github.com/orgs/community/discussions/11692
39-
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HummingbirdLambda', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'StreamingFromEvent', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
39+
examples: "[ 'APIGateway', 'APIGateway+LambdaAuthorizer', 'BackgroundTasks', 'HelloJSON', 'HelloWorld', 'HummingbirdLambda', 'ResourcesPackaging', 'S3EventNotifier', 'S3_AWSSDK', 'S3_Soto', 'Streaming', 'Streaming+Codable', 'ServiceLifecycle+Postgres', 'Testing', 'Tutorial' ]"
4040
archive_plugin_examples: "[ 'HelloWorld', 'ResourcesPackaging' ]"
4141
archive_plugin_enabled: true
4242

Examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ This directory contains example code for Lambda functions.
3838

3939
- **[Streaming](Streaming/README.md)**: create a Lambda function exposed as an URL. The Lambda function streams its response over time. (requires [AWS SAM](https://aws.amazon.com/serverless/sam/)).
4040

41-
- **[StreamingFromEvent](StreamingFromEvent/README.md)**: a Lambda function that combines JSON input decoding with response streaming capabilities, demonstrating the new streaming codable interface (requires [AWS SAM](https://aws.amazon.com/serverless/sam/) or the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
41+
- **[Streaming+Codable](Streaming+Codable/README.md)**: a Lambda function that combines JSON input decoding with response streaming capabilities, demonstrating a streaming codable interface (requires [AWS SAM](https://aws.amazon.com/serverless/sam/) or the [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)).
4242

4343
- **[Testing](Testing/README.md)**: a test suite for Lambda functions.
4444

Examples/StreamingFromEvent/Package.swift renamed to Examples/Streaming+Codable/Package.swift

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,28 @@ import PackageDescription
66
import struct Foundation.URL
77

88
let package = Package(
9-
name: "StreamingFromEvent",
9+
name: "StreamingCodable",
1010
platforms: [.macOS(.v15)],
1111
dependencies: [
1212
// during CI, the dependency on local version of swift-aws-lambda-runtime is added dynamically below
13-
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0-beta.1")
13+
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git", from: "2.0.0-beta.1"),
14+
.package(url: "https://github.com/swift-server/swift-aws-lambda-events.git", from: "1.2.0"),
1415
],
1516
targets: [
1617
.executableTarget(
17-
name: "StreamingFromEvent",
18+
name: "StreamingCodable",
1819
dependencies: [
19-
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime")
20+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
21+
.product(name: "AWSLambdaEvents", package: "swift-aws-lambda-events"),
2022
]
21-
)
23+
),
24+
.testTarget(
25+
name: "Streaming+CodableTests",
26+
dependencies: [
27+
"StreamingCodable",
28+
.product(name: "AWSLambdaRuntime", package: "swift-aws-lambda-runtime"),
29+
]
30+
),
2231
]
2332
)
2433

Examples/StreamingFromEvent/README.md renamed to Examples/Streaming+Codable/README.md

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,33 @@
11
# Streaming Codable Lambda function
22

3-
This example demonstrates how to use the `StreamingLambdaHandlerWithEvent` protocol to create Lambda functions that:
3+
This example demonstrates how to use a `StreamingLambdaHandlerWithEvent` protocol to create Lambda functions, exposed through a FunctionUrl, that:
44

55
1. **Receive JSON input**: Automatically decode JSON events into Swift structs
66
2. **Stream responses**: Send data incrementally as it becomes available
77
3. **Execute background work**: Perform additional processing after the response is sent
88

9-
The example uses the streaming codable interface that combines the benefits of:
9+
## When to Use This Approach
10+
11+
**⚠️ Important Limitations:**
12+
13+
1. **Function URL Only**: This streaming codable approach only works with Lambda functions exposed through [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html)
14+
2. **Limited Request Access**: This approach hides the details of the `FunctionURLRequest` (like HTTP headers, query parameters, etc.) from developers
15+
16+
**Decision Rule:**
17+
18+
- **Use this streaming codable approach when:**
19+
- Your function is exposed through a Lambda Function URL
20+
- You have a JSON payload that you want automatically decoded
21+
- You don't need to inspect HTTP headers, query parameters, or other request details
22+
- You prioritize convenience over flexibility
23+
24+
- **Use the ByteBuffer `StreamingLambdaHandler` approach when:**
25+
- You need full control over the `FunctionURLRequest` details
26+
- You're invoking the Lambda through other means (API Gateway, direct invocation, etc.)
27+
- You need access to HTTP headers, query parameters, or request metadata
28+
- You require maximum flexibility (requires writing more code)
29+
30+
This example balances convenience and flexibility. The streaming codable interface combines the benefits of:
1031
- Type-safe JSON input decoding (like regular `LambdaHandler`)
1132
- Response streaming capabilities (like `StreamingLambdaHandler`)
1233
- Background work execution after response completion

Sources/AWSLambdaRuntime/LambdaStreaming+Codable.swift renamed to Examples/Streaming+Codable/Sources/LambdaStreaming+Codable.swift

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15+
import AWSLambdaEvents
16+
import AWSLambdaRuntime
1517
import Logging
1618
import NIOCore
1719

@@ -71,6 +73,9 @@ public struct StreamingLambdaCodableAdapter<
7173
}
7274

7375
/// Handles the raw ByteBuffer by decoding it and passing to the underlying handler.
76+
/// This function attempts to decode the event as a `FunctionURLRequest` first, which allows for
77+
/// handling Function URL requests that may have a base64-encoded body.
78+
/// If the decoding fails, it falls back to decoding the event "as-is" with the provided JSON type.
7479
/// - Parameters:
7580
/// - event: The raw ByteBuffer event to decode.
7681
/// - responseWriter: The response writer to pass to the underlying handler.
@@ -82,43 +87,20 @@ public struct StreamingLambdaCodableAdapter<
8287
context: LambdaContext
8388
) async throws {
8489

85-
// try to decode the event as a FunctionURLRequest and extract its body
86-
let urlRequestBody = bodyFromFunctionURLRequest(event)
90+
var decodedBody: Handler.Event!
8791

88-
// decode the body or the event as user-provided JSON
89-
let decodedEvent = try self.decoder.decode(Handler.Event.self, from: urlRequestBody ?? event)
92+
// try to decode the event as a FunctionURLRequest, then fetch its body attribute
93+
if let request = try? self.decoder.decode(FunctionURLRequest.self, from: event) {
94+
// decode the body as user-provided JSON type
95+
// this function handles the base64 decoding when needed
96+
decodedBody = try request.decodeBody(Handler.Event.self)
97+
} else {
98+
// try to decode the event "as-is" with the provided JSON type
99+
decodedBody = try self.decoder.decode(Handler.Event.self, from: event)
100+
}
90101

91102
// and pass it to the handler
92-
try await self.handler.handle(decodedEvent, responseWriter: responseWriter, context: context)
93-
}
94-
95-
/// Extract the body payload from a FunctionURLRequest event.
96-
/// This function checks if the event is a valid `FunctionURLRequest` and decodes the body if it is base64 encoded.
97-
/// If the event is not a valid `FunctionURLRequest`, it returns nil.
98-
/// - Parameter event: The raw ByteBuffer event to check.
99-
/// - Returns: The base64 decoded body of the FunctionURLRequest if it is a valid FunctionURLRequest, otherwise nil.
100-
@inlinable
101-
package func bodyFromFunctionURLRequest(_ event: ByteBuffer) -> ByteBuffer? {
102-
do {
103-
// try to decode as a FunctionURLRequest
104-
let request = try self.decoder.decode(FunctionURLRequest.self, from: event)
105-
106-
// if the body is encoded in base64, decode it
107-
if request.isBase64Encoded,
108-
let base64EncodedString = request.body,
109-
// this is the minimal way to base64 decode without importing new dependencies
110-
let decodedData = Data(base64Encoded: base64EncodedString),
111-
let decodedString = String(data: decodedData, encoding: .utf8)
112-
{
113-
114-
return ByteBuffer(string: decodedString)
115-
} else {
116-
return ByteBuffer(string: request.body ?? "")
117-
}
118-
} catch {
119-
// not a FunctionURLRequest, return nil
120-
return nil
121-
}
103+
try await self.handler.handle(decodedBody, responseWriter: responseWriter, context: context)
122104
}
123105
}
124106

@@ -149,8 +131,6 @@ public struct StreamingFromEventClosureHandler<Event: Decodable>: StreamingLambd
149131
}
150132
}
151133

152-
#if FoundationJSONSupport
153-
154134
extension StreamingLambdaCodableAdapter {
155135
/// Initialize with a JSON decoder and handler.
156136
/// - Parameters:
@@ -203,4 +183,3 @@ extension LambdaRuntime {
203183
self.init(handler: adapter, logger: logger)
204184
}
205185
}
206-
#endif // FoundationJSONSupport

Tests/AWSLambdaRuntimeTests/LambdaStreamingCodableTests.swift renamed to Examples/Streaming+Codable/Tests/LambdaStreamingCodableTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import Synchronization
1818
import Testing
1919

2020
@testable import AWSLambdaRuntime
21+
@testable import StreamingCodable
2122

2223
#if canImport(FoundationEssentials)
2324
import FoundationEssentials

Examples/StreamingFromEvent/template.yaml renamed to Examples/Streaming+Codable/template.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ Description: SAM Template for StreamingfromEvent Example
44

55
Resources:
66
# Lambda function
7-
StreamingFromEvent:
7+
StreamingCodable:
88
Type: AWS::Serverless::Function
99
Properties:
10-
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingFromEvent/StreamingFromEvent.zip
10+
CodeUri: .build/plugins/AWSLambdaPackager/outputs/AWSLambdaPackager/StreamingCodable/StreamingCodable.zip
1111
Timeout: 15
1212
Handler: swift.bootstrap # ignored by the Swift runtime
1313
Runtime: provided.al2
@@ -22,4 +22,4 @@ Outputs:
2222
# print Lambda function URL
2323
LambdaURL:
2424
Description: Lambda URL
25-
Value: !GetAtt StreamingFromEventUrl.FunctionUrl
25+
Value: !GetAtt StreamingCodableUrl.FunctionUrl

Sources/AWSLambdaRuntime/FoundationSupport/Vendored/FunctionURL-HTTPType.swift

Lines changed: 0 additions & 110 deletions
This file was deleted.

0 commit comments

Comments
 (0)