powertools-lambda-typescript icon indicating copy to clipboard operation
powertools-lambda-typescript copied to clipboard

Feature request: Injectable error handler for parser

Open codyfrisch opened this issue 5 months ago • 9 comments

Use case

Today when there is a parsing error (except when using safeParse) the error is thrown uncaught. This could be potentially undesired, as we may want to instead return a specific response to the request - I'm thinking specifically API Gateway here but others might benefit too (I just don't have those use cases).

Solution/User Experience

Add an error handler function to ParserOptions. In the decorator add a try/catch around the parse, and in the catch if the error handler function is present, use it and return its value otherwise rethrow the error.

This way we can handle errors JUST from the parse function, rather than some other upstream decorator catching ZodError (or whatever) from where exactly? maybe not just the parser.

Alternative solutions


Acknowledgment

Future readers

Please react with 👍 and your use case to help us understand customer demand.

codyfrisch avatar Jul 21 '25 18:07 codyfrisch

Hi @codyfrisch, thanks for opening this issue.

If I understand correctly, you're suggesting we add something like this:

const errorHandler = (error) => {
  // handle error
}

class Lambda {
 @parser({ schema: SQSSchema, errorHandler })
 async handler(event, context) {
  // your code
 }
}

If this is the case, what's the benefit over doing this (which is the alternative you suggested)?


class Lambda {
 @parser({ schema: SQSSchema, safeParse: true })
 async handler(event, context) {
  if (event.error) return errorHandler(event.error);
  const data = event.data;
  // your code
 }
}

dreamorosi avatar Jul 21 '25 19:07 dreamorosi

Because @parser may not be the final decorator called. Subsequent decorators would need to know how to handle the safeParse result - or yet another decorator would be needed that handles the safeParse result and the customizable error handling.

codyfrisch avatar Jul 21 '25 20:07 codyfrisch

Thanks - our docs don't do a good job here, but the Middy.js middleware and class method decorator are intended for those cases where you want to just parse/validate the event and fail-fast. If you want more control over the parsing and error handling you might want to call parse on the schema directly.

In the Python version of Powertools for AWS we also export a parse function which you'd be able to use like this:

class Lambda {
 async handler(event, context) {
  const result = parse({ event, schema: SQSSchema, safeParse: true })
  if (result.error) return errorHandler(event.error);
  const data = result.data;
  // your code
 }
}

Would something like this get closer to what you need?

I'm not necessarily pushing back against your idea, as a matter of fact I encountered the same issue you're describing in another codebase myself and I ended up implementing a custom decorator that catches/formats the error into an API Gateway response, so I can see the problem.

At this stage I'm primarily trying to find ways to address your issue without adding a new feature because that will require further discussion and most likely the involvement of other Powertools for AWS versions for feature parity's sake.

dreamorosi avatar Jul 22 '25 07:07 dreamorosi

Hey there! Good to see you again bringing good ideas @codyfrisch to improve the Powertools experience. I really appreciate your collaboration.

While I'll let to you both to discuss about TS experience, middleware and TS ecosystem, I'd like to reply to @dreamorosi's comment.

At this stage I'm primarily trying to find ways to address your issue without adding a new feature because that will require further discussion and most likely the involvement of other Powertools for AWS versions for feature parity's sake.

This doesn't necessarily apply to Python, as the languages work slightly differently. When using the parser in the Python version, the customer has two methods for validating the event:

Fast fail Customers will use the @event_parser decorator to decorate the Lambda handler, and there is no way to catch this exception. Customers can wrapper this decorator inside another decorator, but I've never seen that.

Parsing the event using custom logic Customers can use the parse function to validate the event within the Lambda handler. In this case, they can create custom logic to catch exceptions and do some stuff.

In both cases, we will throw 1 custom exception:

InvalidModelTypeError - If we are unable to "compile" the TypeAdapter object and generate the CoreSchema, or if the provided model is not a valid Pydantic object. Imagine you passed a string as the model, this will fail.

If the schema is valid and the object is a valid Pydantic object, but something goes wrong, we raise it directly from Pydantic. In most cases, this will raise a ValidationError because there was some error parsing/dumping the data into the model and customers are used to it.

Just for additional context, when we released v3 we tried to wrap Pydantic exceptions into a PowertoolsCustomException (i don't remember exactly the name) but customers complained about that and we reverted.

Pls let me know if I can help with anything else.

leandrodamascena avatar Jul 22 '25 08:07 leandrodamascena

Opz, I saw @dreamorosi covered it here: https://github.com/aws-powertools/powertools-lambda-typescript/issues/4191#issuecomment-3101499549

My bad, forget about my comment.

leandrodamascena avatar Jul 22 '25 08:07 leandrodamascena

At this stage I'm primarily trying to find ways to address your issue without adding a new feature because that will require further discussion and most likely the involvement of other Powertools for AWS versions for feature parity's sake.

That discussion actually what I was trying to spur by making this a feature request and not a general question (how do I...)! I personally have already worked around this with a custom parser decorator used with the Powertools schemas.

The main reason I don't parse in the handler, is I have multiple additional decorators that need the parsed event (in my case, I deserialize the body as well with with the Zod schema I use). I much prefer this to boilerplate inside the handler method - it makes it obvious what is meaningful business logic.

codyfrisch avatar Jul 22 '25 13:07 codyfrisch

Thanks again for clarifying - interesting, I don't think we had considered the case in which other decorators might want to access the parsed payload - I think it could make sense to add this error handler then.

Before making a decision on whether to put this on the backlog, Is there any chance that you could share an example of this type of decorator?

This would be helpful to understand the use case better.

dreamorosi avatar Jul 24 '25 21:07 dreamorosi

Before making a decision on whether to put this on the backlog, Is there any chance that you could share an example of this type of decorator?

The most obvious one in my stack is the one which pulls out correlation IDs from the now deserialized body and appends the keys to the logger instance, as well as data from the lambda authorizer context in the API gateway request context. This one ought to relatively obvious in the code department :) I need the deserialized body for this - and the JSONStringified function solves this very nicely.

Now that we have actual data to work with I can do further processing such as:

Another one uses data from the authorizer context to set usage tracking instance (for customer quotas) and then that instance is used throughout the code to measure the number of billable operations. Then on return the decorator flushes that usage data to database. But this also needs details from the body about what entity in the external service triggered the request, and then we can in our database attach some history for the customer to see on a dashboard.

Another still in the works takes data from the body and authorization header, does some db queries to find access control lists and then can decide how to handle the request. While the auth header gives us some detail like the account and we can make certain early decisions in the lambda authorizer function - that doesn't have access to the body where additional details needed for these decisions is found.

Last one is one for an API client that captures a short lived API token send with the webhook (which the lambda authorizer extracts from the auth header jwt and puts in the authorizer context). This decorator then tears down the http client instance in the API client that encloses the token on return of the original call (finally block).

All of this is wrapped around some 70+ lambda handlers - hence why decorators are really handy.

*The payloads of these requests are from a third party service that I only have control over the body contents to a limited degree (The data I request in the webhook configuration)

codyfrisch avatar Jul 25 '25 00:07 codyfrisch

This issue has not received a response in 2 weeks. If you still think there is a problem, please leave a comment to avoid the issue from automatically closing.

github-actions[bot] avatar Aug 08 '25 00:08 github-actions[bot]