Skip to content

Commit 9487a09

Browse files
authored
Lambda Handler errors now reports the root error in field errorType rather than "FunctionError" constant (#587)
### Motivation: Fix for Issue [#580](#580), by making it so that the `errorType` in failed requests will be the type of the error entity, rather than a hardcoded string of `FunctionError`. This allows orchestration within step functions that perform retry/catch logic based on different error output types. ### Modifications: At a high level, the issue is that swift-aws-lambda-runtime, when an error is thrown, outputs the errorType as hardcoded to FunctionError. You can see that [here](https://github.com/swift-server/swift-aws-lambda-runtime/blob/main/Sources/AWSLambdaRuntime/LambdaRuntimeClient%2BChannelHandler.swift#L337): ``` let errorResponse = ErrorResponse(errorType: Consts.functionError, errorMessage: "\(error)") ``` This PR changes this for all cases to output the type of the error, rather than the hardcoded string: ``` let errorResponse = ErrorResponse(errorType: "\(type(of: error))", errorMessage: "\(error)") ``` Now, I will show 2 examples with this solution: ``` let runtime = LambdaRuntime { (event: Input, context: LambdaContext) in enum MyTestErrorType: Error { case testError } throw MyTestErrorType.testError } // outputs {"errorType":"MyTestErrorType","errorMessage":"testError"} ``` ``` let dynamoDB: DynamoDB = DynamoDB(client: .init()) let runtime = LambdaRuntime { (event: Input, context: LambdaContext) in let _ = try await dynamoDB.putItem(DynamoDB.PutItemInput(item: [:], tableName: "")) return Output() } // outputs {"errorType":"AWSClientError","errorMessage":"ValidationError: Length of PutItemInput.tableName (0) is less than minimum allowed value 1."} ```
1 parent 22f9f6d commit 9487a09

File tree

3 files changed

+75
-2
lines changed

3 files changed

+75
-2
lines changed

Sources/AWSLambdaRuntime/LambdaRuntimeClient+ChannelHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ internal final class LambdaChannelHandler<Delegate: LambdaChannelHandlerDelegate
334334
self.reusableErrorBuffer!.clear()
335335
}
336336

337-
let errorResponse = ErrorResponse(errorType: Consts.functionError, errorMessage: "\(error)")
337+
let errorResponse = ErrorResponse(errorType: "\(type(of: error))", errorMessage: "\(error)")
338338
// TODO: Write this directly into our ByteBuffer
339339
let bytes = errorResponse.toJSONBytes()
340340
self.reusableErrorBuffer!.writeBytes(bytes)

Sources/AWSLambdaRuntime/Utils.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ enum Consts {
2424
static let postResponseURLSuffix = "/response"
2525
static let postErrorURLSuffix = "/error"
2626
static let postInitErrorURL = "\(apiPrefix)/runtime/init/error"
27-
static let functionError = "FunctionError"
2827
static let initializationError = "InitializationError"
2928
}
3029

Tests/AWSLambdaRuntimeTests/LambdaRuntimeClientTests.swift

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,4 +330,78 @@ struct LambdaRuntimeClientTests {
330330
}
331331
}
332332
}
333+
334+
@Test(
335+
"reportError() sends the correct errorType for different Error types"
336+
)
337+
@available(LambdaSwift 2.0, *)
338+
func testReportErrorReturnsProperErrorType() async throws {
339+
// Custom error types for testing
340+
struct MyCustomError: Error {
341+
let message: String
342+
}
343+
344+
enum MyEnumError: Error {
345+
case anotherCase(String)
346+
}
347+
348+
struct ErrorReportingBehavior: LambdaServerBehavior {
349+
let requestId = UUID().uuidString
350+
let event = "error-testing"
351+
let expectedErrorType: String
352+
353+
func getInvocation() -> GetInvocationResult {
354+
.success((self.requestId, self.event))
355+
}
356+
357+
func processResponse(requestId: String, response: String?) -> Result<String?, ProcessResponseError> {
358+
Issue.record("should not process response, expecting error report")
359+
return .failure(.internalServerError)
360+
}
361+
362+
func processError(requestId: String, error: ErrorResponse) -> Result<Void, ProcessErrorError> {
363+
#expect(self.requestId == requestId)
364+
#expect(
365+
error.errorType == self.expectedErrorType,
366+
"Expected errorType '\(self.expectedErrorType)' but got '\(error.errorType)'"
367+
)
368+
return .success(())
369+
}
370+
371+
func processInitError(error: ErrorResponse) -> Result<Void, ProcessErrorError> {
372+
Issue.record("should not report init error")
373+
return .failure(.internalServerError)
374+
}
375+
}
376+
377+
// Test with MyCustomError
378+
try await withMockServer(behaviour: ErrorReportingBehavior(expectedErrorType: "MyCustomError")) { port in
379+
let configuration = LambdaRuntimeClient.Configuration(ip: "127.0.0.1", port: port)
380+
381+
try await LambdaRuntimeClient.withRuntimeClient(
382+
configuration: configuration,
383+
eventLoop: NIOSingletons.posixEventLoopGroup.next(),
384+
logger: self.logger
385+
) { runtimeClient in
386+
let (_, writer) = try await runtimeClient.nextInvocation()
387+
let error = MyCustomError(message: "Something went wrong")
388+
try await writer.reportError(error)
389+
}
390+
}
391+
392+
// Test with MyEnumError
393+
try await withMockServer(behaviour: ErrorReportingBehavior(expectedErrorType: "MyEnumError")) { port in
394+
let configuration = LambdaRuntimeClient.Configuration(ip: "127.0.0.1", port: port)
395+
396+
try await LambdaRuntimeClient.withRuntimeClient(
397+
configuration: configuration,
398+
eventLoop: NIOSingletons.posixEventLoopGroup.next(),
399+
logger: self.logger
400+
) { runtimeClient in
401+
let (_, writer) = try await runtimeClient.nextInvocation()
402+
let error = MyEnumError.anotherCase("test")
403+
try await writer.reportError(error)
404+
}
405+
}
406+
}
333407
}

0 commit comments

Comments
 (0)