aws-lambda-rust-runtime: awkward HandlerError requirements

I’m finding that, in practice, the new HandlerError’ type constraints may have made error handling hard/awkward to satisfy for users

common cases I run into is trying to represent a foreign crate type for with lambda_runtime_errors::LambdaErrorExt. Since the Fail crate is becoming more common I think that’s okay, but still rough if the foreign crates error type sticks to the standard library error type

I find myself often cluttering map_err(HandlerError::new) code with this

// I don't control the Err type of foo_bar as its defined in another crate
// so I can't impl lambda_runtime_errors::LambdaErrorExt for ThatError { ... }
foo_bar(input).map_err(HandlerError::new)? // fails to compile because no impl for lambda_runtime_errors::LambdaErrorExt

This is unfortunate for users because often these are simple hello world integrate xxx with lambda cases where the majority of the code written is satisfying the trait constraints on errors and not the code what brings business value.

I understand why this constraint exists but I want to revisit the value for users this is providing vs what its costing to author lambdas.

I’ve ran into this enough that I wanted to reopen the floor for alternative options.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 9
  • Comments: 25 (17 by maintainers)

Most upvoted comments

Just looking at this from a high level, my recommendation would be to stick with std::error::Error. The failure crate is far from standard.

I would set bounds to: E: Into<Box<std::error::Error + Send + Sync>>. Then provide:

struct HandlerError {
    source: Box<Error + Send + Sync>,
    name: &'static str,
}

From the lambda runtime, to get the error name would be a two step process:

  • First, walk the source list until you find an error of type HandlerError. If you find one, use that as the name.
  • If no HandlerError was found, use the Debug implementation and, assume the format is TypeName { [fields] }, parse out the name.
  • Fall back to “unknown error”.

Is it great? No… but my gut reaction is that it would work better than forcing the error to be HandlerError as no web framework or whatever would have that type, so getting access to the name of the root error would be tricky.

Then, once type_name is stable, use that.

Update on this. As mentioned above, there has been a lot of changes on master that diverge from what’s currently up on crates.io. These reflect some learnings, some simplification of the previous design, ergonomic improvements, and first class support for async/awaitable handlers.

The error ergonomics issues have mostly been resolved. The http module has been brought up to speed in this pull
https://github.com/awslabs/aws-lambda-rust-runtime/pull/217

Mostly anything impl std error will now do for errors.

Here’s an example

https://github.com/softprops/aws-lambda-rust-runtime/blob/a3852265f595dec6e8e993ce12b1080f2a38e0af/lambda-http/examples/basic.rs#L3

closing. I’m really happy with the state of the master branch

Looking forward to the new Error improvements. @rib thanks for the snippet, I was fighting that pretty hard as well. 😃

I’d like to come to a solution for this particular problem more quickly. One way to do that would be to have narrow problems keep narrow focus to avoid designing suspension bridges for potholes. The concrete and specific problem here is: the design of the type that all errors would eventually need to materialize into.

That’s a good point. I think the design I proposed is an acceptable long-term solution, but in the interest of addressing pain points now I think your proposal of a HandlerError::named(err, "MyErrorName") is a good first step.

If I’m understanding the proposed solution above correctly, it would be to make it such that handler implementations could return different types of errors where currently handlers defined as anything that satisfies Fail + LambdaErrorExt + Display + Send + Sync. With the proposed solutions above, handler adapters (I think) would still have to eventually materialize a single error type at the end of the chain for the runtime to return to the lambda api.

That’s correct—for folks that don’t care about dashboarding error types, we’d rely on HandlerError::default() (or just a String, as that allows us to place the HandlerError behind a feature flag + cut down on compile times. Like you said, “At a minimum, that’s just an error message and type.”).

For that the handler interface changes I’d like to see a gh issue laying out the problem so that we can make sure we’re applying a fitting solution. Representing errors may be part of that problem but I see redesiging handler types entirely as orthogonal a uniform error type.

That’s a good point. I’ll open a new issue with the handler design + close out https://github.com/awslabs/aws-lambda-rust-runtime/pull/62, as it’s pretty different now.

Something did catch my eye above though which is very near and dear to my heart. There was mention of cutting down compile times… 😃 I’m very interested in that topic.

Glad you noticed that 😀. I’ve noticed that because of Tower’s insistence on a bunch of tiny crates that, in composition, provide similar feature sets to Hyper, Cargo is able to compile these tiny crates in parallel.