Closed tannerntannern closed 2 months ago
we're open to suggestions. I don't see any other workaround either.
One idea that comes to mind is making the blanket Into<Diagnostic>
implementation opt-in. I'm not a Rust pro, so take this with a grain of salt:
pub trait IntoDiagnostic {}
impl<T: Display> IntoDiagnostic for T {}
impl<'a, T> From<T> for Diagnostic<'a>
where
T: Display + IntoDiagnostic
{
// existing implementation
}
With this approach, IntoDiagnostic
is automatically implemented for Display
, and Into<Diagnostic>
is automatically implemented for Display + IntoDiagnostic
. When IntoDiagnostic
is in scope, the current behavior is maintained. The crucial difference is that crate consumers can choose whether they want to bring IntoDiagnostic
into scope. If they don't, they're required to implement Into<Diagnostic>
themselves, but can avoid the conflicting implementation. This is a breaking change though, so not sure if there's an appetite for that.
Another option could be waiting for negative trait bounds to stabilize so you can do something like this:
pub trait ErrorType {
fn error_type(&self) -> String;
}
impl<'a, T> From<T> for Diagnostic<'a>
where
T: Display + ErrorType
{
fn from(value: T) -> Self {
Diagnostic {
error_type: std::borrow::Cow::Borrowed(value.error_type()),
error_message: std::borrow::Cow::Owned(format!("{value}")),
}
}
}
impl<'a, T> From<T> for Diagnostic<'a>
where
T: Display + !ErrorType
{
// existing implementation
}
With this approach, you could still lean on the convenience of having Into<Diagnostic>
implemented for you. But if you want to change the error_type
field, you can provide an ErrorType
implementation. The upside here is that the change would be non-breaking, however it relies on negative trait bounds which might be a non-starter.
With this approach,
IntoDiagnostic
is automatically implemented forDisplay
, andInto<Diagnostic>
is automatically implemented forDisplay + IntoDiagnostic
. WhenIntoDiagnostic
is in scope, the current behavior is maintained. The crucial difference is that crate consumers can choose whether they want to bringIntoDiagnostic
into scope. If they don't, they're required to implementInto<Diagnostic>
themselves, but can avoid the conflicting implementation. This is a breaking change though, so not sure if there's an appetite for that.
I think this is the right solution, other extensions to the runtime already follow this principle, like the RequestExt in lambda-http.
I'm not concerned about backwards compatibility, since we're not giving any guarantees until the runtime is officially supported, and this is something fairly new.
Do you mind opening a PR with your proposal? We'll really appreciate the contribution.
I've tried your proposal but it doesn't work because it has the same problem since everything that implements Display automatically implements IntoDiagnostic. I went with a different implementation in #897 and I added an example on how to implement Diagnostic based on your snippet.
Thanks for looking into this @calavera! My bad on the proposal oversight. I thought as long as IntoDiagnostic
was kept out of scope it would work, but I should have tested it more thoroughly.
No worries, thanks for bringing this issue up!
This issue is now closed. Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one.
The
Diagnostic
docs say:I'm trying to do just that. However my error container already has a
Display
implementation (viathiserror
). Because of the blanketimpl<'a, T> From<T> for Diagnostic<'a> where T: Display
, I run into a conflicting trait implementations error.The error makes perfect sense, but I also see no way around it without removing the
Display
implementation from my code, which I'd like to keep. I feel like it's not that unusual to want a customInto<Diagnostic>
forDisplay
types. Do I have other options here?As a motivating example, I'm working in an application that uses AWS step functions, and our team wants to distinguish between retryable and non-retryable errors so the state machine can automatically retry failed lambdas when appropriate. For logging purposes, we don't care whether the error is retryable, but we need the information to bubble up to the
errorType
of the lambda error output. Here's a snippet: