Closed kikuomax closed 6 months ago
@evbo Have you tried something like the following?
#[derive(thiserror::Error, Debug)]
pub enum MyError {
#[error("some error: {0}")]
SomeError(String),
// ... other variants
}
impl<'a> Into<Diagnostic<'a>> for MyError {
fn into(self) -> Diagnostic<'a> {
match self {
Self::SomeError(s) => Diagnostic {
error_type: Cow::Borrowed("SomeError"),
error_message: Cow::Owned(s),
},
// ... other variants
}
}
}
The above will cause conflict between From<Display> for Diagnostic
and Into<Diagnostic> for MyError
, because MyError
implements derived Display
. So you cannot directly implement Diagnostic
for MyError
; more generally, any type implementing std::error::Error
.
A workaround in this case may be to define a wrapper struct for your error type and implement Diagnostic
for the wrapper like in the example of Diagnostic
:
struct ErrorResponse(MyError);
impl<'a> Into<Diagnostic<'a>> for ErrorResponse {
fn into(self) -> Diagnostic<'a> {
match self.0 {
ErrorResponse::SomeError(s) => Diagnostic {
error_type: Cow::Borrowed("SomeError"),
error_message: Cow::Owned(s),
},
// ... other variants
}
}
}
Display
must not be implemented for ErrorResponse
to avoid the same conflict.
Obviously, I should refine the documentation.
@kikuomax I take back my earlier messages. I think you're right - this can be solved with better docs.
I think the best ergonomics are achieved when function_handler
returns the error and then you map_err
to the top-level struct by intercepting what gets returned to lambda_runtime::run
as shown here:
https://docs.aws.amazon.com/lambda/latest/dg/rust-handler.html#rust-shared-state
@evbo I am planning a PR to improve the documentation. How about having the following in the API documentation? Does it make better sense?
Diagnostic
is automatically generated from any types that implement Display
; e.g., Error
. The default behavior puts the type name of the original error obtained with std::any::type_name
into error_type
, which may not be reliable for conditional error handling. You can define your own error container that implements Into<Diagnostic>
if you need more accurate error types.
Example:
use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent};
#[derive(Debug)]
struct ErrorResponse(Error);
impl<'a> Into<Diagnostic<'a>> for ErrorResponse {
fn into(self) -> Diagnostic<'a> {
Diagnostic {
error_type: "MyError".into(),
error_message: self.0.to_string().into(),
}
}
}
async fn function_handler(_event: LambdaEvent<()>) -> Result<(), Error> {
// ... do something
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), Error> {
lambda_runtime::run(service_fn(|event| async {
function_handler(event).await.map_err(ErrorResponse)
})).await
}
The container ErrorResponse
in the above snippet is necessary because you cannot directly implement Into<Diagnostic>
for types that implement Error
. For instance, the following code will not compile:
ⓘ compile_fail
use lambda_runtime::Diagnostic;
#[derive(Debug)]
struct MyError(String);
impl std::fmt::Display for MyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MyError: {}", self.0)
}
}
impl std::error::Error for MyError {}
// ERROR! conflicting implementations of trait `Into<Diagnostic<'_>>` for type `MyError`
impl<'a> Into<Diagnostic<'a>> for MyError {
fn into(self) -> Diagnostic<'a> {
Diagnostic {
error_type: "MyError".into(),
error_message: self.0.into(),
}
}
}
Issue #, if available:
824
Description of changes:
F::Error
ofRuntime::run
andrun
is required to implementInto<Diagnostic<'a>>
instead ofstd::fmt::Display
Into<Diagnostic<'a>>
is implemented for any types that implementstd::fmt::Display
as a fallback and for backward compatibilityDiagnostic
and its fields become public so that users can customize error responseserror_type
anderror_message
ofDiagnostic
are wrapped instd::borrow::Cow
so that they can hold both borrowed and owned strings. An example of the situation where an owned string is necessary is in the fallback implementation ofFrom<Display>
forDiagnostic
, which generateserror_message
insidefrom
method and it has to outlive the method call.By submitting this pull request