-
Notifications
You must be signed in to change notification settings - Fork 353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Regression in v0.12.0: External error types no longer work with lambda_runtime::run #901
Comments
Feel free to open a PR to improve the support of those errors 🙏 |
I don't see any way to fix this without compromising on #880. You will need to choose whether you want to automatically support error types that implement I will list a few different options for how to proceed from here. Let me know which one you prefer, and I will implement a PR for that. This is the context as I see and and how I will evaluate the different options:
Option 1: Keeping things as they areWith the current code, all error types besides the few types blessed in Your choices are to either convert to one of the few blessed types:
Or alternatively you can make you can define your own custom error type like this: struct MyErrorType(...);
impl<'a> From<MyErrorType> for Diagnostic<'a> {
fn from(value: MyErrorType) -> Diagnostic<'a> {
...
}
} Note, you cannot implement Option 2: Partially revert #897 to re-add the blanked implReverting would fix the majority use-case, but it would re-introduce friction in the minority use-case. It would become difficult to use a custom conversion for your own error types that already implement Display. The only real way to achieve custom conversion for your own types would be something like this: #[derive(Debug, thiserror::Error)]
struct MyError { ... }
struct AnnoyingErrorWrapper(MyError);
impl<'a> From<AnnoyingErrorWrapper> for Diagnostic<'a> {
fn from(value: AnnoyingErrorWrapper) -> Diagnostic<'a> {
...
}
}
impl From<MyError> for AnnoyingErrorWrapper {
fn from(value: MyError) -> AnnoyingErrorWrapper {
AnnoyingErrorWrapper(value)
}
} Option 3: Partial revert with ergonomic improvementsAs I see it, there is no way to introduce some friction to either the majority or minority use-cases, however that friction could be made much smaller than in either option 1 or 2. I think the best way would be to re-add the blanket impl, but also make it easier to return custom diagnostics. I see two non-conflicting ways we could reduce the friction. Friction reduction 1:It is currently impossible to directly return a We could fix this by using a custom trait async fn handler(event: lambda_runtime::LambdaEvent<()>) -> Result<(), Diagnostic<'static>> {
real_handler(event)
.await
.map_err(|e: MyError| -> Diagnostic {
...
})
}
async fn real_handler(_event: lambda_runtime::LambdaEvent<()>) -> Result<(), MyError> {
...
} Friction reduction 2:We could also add an additional type impl<'a> From<MyError> for CustomDiagnostic<'a> {
fn from(value: MyError) -> Self {
todo!()
}
}
async fn handler(event: lambda_runtime::LambdaEvent<()>) -> Result<(), CustomDiagnostic<'static>> {
func1(&event).await?;
func2(&event).await?;
Ok(())
}
async fn func1(event: &lambda_runtime::LambdaEvent<()>) -> Result<(), MyError> {
todo!()
}
async fn func2(event: &lambda_runtime::LambdaEvent<()>) -> Result<(), MyError> {
todo!()
} Friction reduction 3:We could create a macro for creating custom conversion types. It could look something like this: create_error_wrapper! { MyError => AnnoyingErrorWrapper with converter }
fn converter(value: MyError) -> Diagnostic<'static> {
...
} This would then unfold to this: struct AnnoyingErrorWrapper(MyError);
impl<'a> From<AnnoyingErrorWrapper> for Diagnostic<'a> {
fn from(value: AnnoyingErrorWrapper) -> Diagnostic<'a> {
converter(value.0)
}
}
impl From<MyError> for AnnoyingErrorWrapper {
fn from(value: MyError) -> AnnoyingErrorWrapper {
AnnoyingErrorWrapper(value)
}
} Option 4: Ergonomic improvements without a blanket implIt would also be possible to do some of the ergonomic improvements without re-adding the blanket impl. Specifically the first and third ergonomic improvements would still make sense in this case. ConclusionPersonally I think the best option is Option 3, with friction reduction number 1. I would not mind also adding number 2 and 3 though. |
Let's start with option 1. I don't fully understand what the benefit of option 2 is(I might need more information), and I'd rather avoid macros unless they're absolutely necessary. Additionally, I think we can add other common error implementations, like |
I am confused about whether you mean options or the friction reductions I've discussed. Basically my outline had are two orthogonal choices:
My suggestion was to bring back the blanket impl to benefit the majority case, but then add some ergonomic improvements to make the minority case less cumbersome. |
I consider giving people the ability to completely customize the error to return more important that returning string messages for any type of error. We're not going to bring back the blanket implementation. Returning an anyhow message is not that much complicated with the current version, this seems to work: async fn function_handler(event: LambdaEvent<Request>) -> Result<(), Error> {
Err(anyhow!("ooops").into())
} We've never guaranteed backwards compatibility for this exact reason, we need to ensure that we can provide access to all features that Lambda provides, including returning the right error payloads, and we need to be able to make these kind of breaking changes before we stabilize the runtime. I'm happy to merge further improvements on top of the current implementation though. |
Sure, but if you do that, then you:
|
As a naive user who until the upgrade to 0.12 was happily returning some impl of |
I would argue that both
@tim3z error types are not a customization, they are part of Lambda's feature set. Unfortunately, we've been ignoring it for way too long, and now we're paying the price. This is definitely on me for not approaching the problem sooner. This has also created some bad practices, like parsing the error message to observe and understand why a function call failed, where the error_type exists for this specific reason. As I mention earlier, I'm happy to review improvements upon this breaking change to improve the experience, as long as defining diagnostics doesn't get buried again. [EDIT] The best possible way to find a solution is through PRs. There are several error handling examples in the |
I've made some improvements in #907. Let me know what you think. |
This issue is now closed. Comments on closed issues are hard for our team to see. |
With the release of v0.12.0, it is no longer possible to use error types defined externally to your own crate. This is caused by the inclusion of #897.
Example:
Note that it is not possible to implement
Into<Diagnostic<'a>>
yourself for any of these types because of the orphan rules.The text was updated successfully, but these errors were encountered: