Repository: awslabs/aws-lambda-rust-runtime (language: rust)

Issue:
Custom `impl Into<Diagnostic>` for types that already implement `Display`
The `Diagnostic` docs say:

> You can define your own error container that implements `Into<Diagnostic>` if you need to handle errors based on error types.

I'm trying to do just that.  However my error container already has a `Display` implementation (via `thiserror`).  Because of the blanket `impl<'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 custom `Into<Diagnostic>` for `Display` 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:
```rust
#[derive(Debug, thiserror::Error)]
pub enum ExecutionError {
    #[error(transparent)]
    Retryable(#[from] RetryableExecutionError),
    #[error(transparent)]
    NonRetryable(#[from] NonRetryableExecutionError),
}

#[derive(Debug, thiserror::Error)]
pub enum RetryableExecutionError {
    #[error("transient database error: {0}")]
    DatabaseError(String),
    #[error("unexpected error: {0}")]
    Unexpected(String),
}

#[derive(Debug, thiserror::Error)]
pub enum NonRetryableExecutionError {
    // snip
}

impl<'a> From<ExecutionError> for Diagnostic<'a> {
    fn from(value: ExecutionError) -> Diagnostic<'a> {
        let (error_type, error_message) = match value {
            | Self::Retryable(err) => ("Retryable", err.to_string()),
            | Self::NonRetryable(err) => ("NonRetryable", err.to_string()),
        };
        Diagnostic {
            error_type: Cow::Owned(error_type.into()),
            error_message: Cow::Owned(error_message.into()),
        }
    }
}
```


Discussion / hints:
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:

```rust
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:

```rust
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 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.

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.

Fix patch:
diff --git a/Cargo.toml b/Cargo.toml
index 0b01ee05..c55f0e60 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -14,7 +14,11 @@ exclude = ["examples"]
 [workspace.dependencies]
 base64 = "0.22"
 bytes = "1"
-chrono = "0.4.35"
+chrono = { version = "0.4.35", default-features = false, features = [
+    "clock",
+    "serde",
+    "std",
+] }
 futures = "0.3"
 futures-channel = "0.3"
 futures-util = "0.3"
diff --git a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml
index 116ab8ef..bd41a01a 100644
--- a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml
+++ b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml
@@ -1,4 +1,5 @@
 [workspace]
+resolver = "2"
 
 members = [
     "producer",
diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-diagnostic/.gitignore
new file mode 100644
index 00000000..c41cc9e3
--- /dev/null
+++ b/examples/basic-error-diagnostic/.gitignore
@@ -0,0 +1,1 @@
+/target
\ No newline at end of file
diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-diagnostic/Cargo.toml
new file mode 100644
index 00000000..b81ef730
--- /dev/null
+++ b/examples/basic-error-diagnostic/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "basic-error-diagnostic"
+version = "0.1.0"
+edition = "2021"
+
+# Starting in Rust 1.62 you can use `cargo add` to add dependencies 
+# to your project.
+#
+# If you're using an older Rust version,
+# download cargo-edit(https://github.com/killercup/cargo-edit#installation) 
+# to install the `add` subcommand.
+#
+# Running `cargo add DEPENDENCY_NAME` will
+# add the latest version of a dependency to the list,
+# and it will keep the alphabetic ordering for you.
+
+[dependencies]
+
+lambda_runtime = { path = "../../lambda-runtime" }
+serde = "1"
+thiserror = "1.0.61"
+tokio = { version = "1", features = ["macros"] }
diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-diagnostic/README.md
new file mode 100644
index 00000000..b9bf1827
--- /dev/null
+++ b/examples/basic-error-diagnostic/README.md
@@ -0,0 +1,13 @@
+# AWS Lambda Function Error handling example
+
+This example shows how to implement the `Diagnostic` trait to return a specific `error_type` in the Lambda error response. If you don't use the `error_type` field, you don't need to implement `Diagnostic`, the type will be generated based on the error type name.
+
+## Build & Deploy
+
+1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation)
+2. Build the function with `cargo lambda build --release`
+3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE`
+
+## Build for ARM 64
+
+Build the function with `cargo lambda build --release --arm64`
diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-diagnostic/src/main.rs
new file mode 100644
index 00000000..11f68d4b
--- /dev/null
+++ b/examples/basic-error-diagnostic/src/main.rs
@@ -0,0 +1,37 @@
+use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent};
+use serde::Deserialize;
+use thiserror;
+
+#[derive(Deserialize)]
+struct Request {}
+
+#[derive(Debug, thiserror::Error)]
+pub enum ExecutionError {
+    #[error("transient database error: {0}")]
+    DatabaseError(String),
+    #[error("unexpected error: {0}")]
+    Unexpected(String),
+}
+
+impl<'a> From<ExecutionError> for Diagnostic<'a> {
+    fn from(value: ExecutionError) -> Diagnostic<'a> {
+        let (error_type, error_message) = match value {
+            ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()),
+            ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()),
+        };
+        Diagnostic {
+            error_type: error_type.into(),
+            error_message: error_message.into(),
+        }
+    }
+}
+
+/// This is the main body for the Lambda function
+async fn function_handler(_event: LambdaEvent<Request>) -> Result<(), ExecutionError> {
+    Err(ExecutionError::Unexpected("ooops".to_string()))
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Error> {
+    lambda_runtime::run(service_fn(function_handler)).await
+}
diff --git a/examples/basic-error-handling/README.md b/examples/basic-error-handling/README.md
index 498f8a50..5eef4207 100644
--- a/examples/basic-error-handling/README.md
+++ b/examples/basic-error-handling/README.md
@@ -1,4 +1,6 @@
-# AWS Lambda Function example
+# AWS Lambda Function Error handling example
+
+This example shows how to return a custom error type for unexpected failures.
 
 ## Build & Deploy
 
diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs
index 528d6f02..3bc76936 100644
--- a/examples/basic-error-handling/src/main.rs
+++ b/examples/basic-error-handling/src/main.rs
@@ -1,7 +1,7 @@
 /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda
 use lambda_runtime::{service_fn, tracing, Error, LambdaEvent};
 use serde::{Deserialize, Serialize};
-use serde_json::{json, Value};
+use serde_json::json;
 use std::fs::File;
 
 /// A simple Lambda request structure with just one field
@@ -59,11 +59,11 @@ async fn main() -> Result<(), Error> {
 }
 
 /// The actual handler of the Lambda request.
-pub(crate) async fn func(event: LambdaEvent<Value>) -> Result<Value, Error> {
+pub(crate) async fn func(event: LambdaEvent<Request>) -> Result<Response, Error> {
     let (event, ctx) = event.into_parts();
 
     // check what action was requested
-    match serde_json::from_value::<Request>(event)?.event_type {
+    match event.event_type {
         EventType::SimpleError => {
             // generate a simple text message error using `simple_error` crate
             return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!")));
@@ -94,7 +94,7 @@ pub(crate) async fn func(event: LambdaEvent<Value>) -> Result<Value, Error> {
                 msg: "OK".into(),
             };
 
-            return Ok(json!(resp));
+            return Ok(resp);
         }
     }
 }
diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml
index 6d496b43..d9774104 100644
--- a/lambda-events/Cargo.toml
+++ b/lambda-events/Cargo.toml
@@ -18,11 +18,7 @@ edition = "2021"
 [dependencies]
 base64 = { workspace = true }
 bytes = { workspace = true, features = ["serde"], optional = true }
-chrono = { workspace = true, default-features = false, features = [
-  "clock",
-  "serde",
-  "std",
-], optional = true }
+chrono = { workspace = true, optional = true }
 flate2 = { version = "1.0.24", optional = true }
 http = { workspace = true, optional = true }
 http-body = { workspace = true, optional = true }
diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs
index 265f5ef0..90e59867 100644
--- a/lambda-http/src/lib.rs
+++ b/lambda-http/src/lib.rs
@@ -68,6 +68,7 @@ pub use http::{self, Response};
 /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions.
 #[cfg(feature = "tracing")]
 pub use lambda_runtime::tracing;
+use lambda_runtime::Diagnostic;
 pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service};
 use request::RequestFuture;
 use response::ResponseFuture;
@@ -193,7 +194,7 @@ where
     S: Service<Request, Response = R, Error = E>,
     S::Future: Send + 'a,
     R: IntoResponse,
-    E: std::fmt::Debug + std::fmt::Display,
+    E: std::fmt::Debug + for<'b> Into<Diagnostic<'b>>,
 {
     lambda_runtime::run(Adapter::from(handler)).await
 }
diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs
index 217c4564..df569129 100644
--- a/lambda-http/src/streaming.rs
+++ b/lambda-http/src/streaming.rs
@@ -5,8 +5,9 @@ use crate::{request::LambdaRequest, RequestExt};
 use bytes::Bytes;
 pub use http::{self, Response};
 use http_body::Body;
+use lambda_runtime::Diagnostic;
 pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse};
-use std::fmt::{Debug, Display};
+use std::fmt::Debug;
 use std::pin::Pin;
 use std::task::{Context, Poll};
 use tokio_stream::Stream;
@@ -20,7 +21,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(),
 where
     S: Service<Request, Response = Response<B>, Error = E>,
     S::Future: Send + 'a,
-    E: Debug + Display,
+    E: Debug + for<'b> Into<Diagnostic<'b>>,
     B: Body + Unpin + Send + 'static,
     B::Data: Into<Bytes> + Send,
     B::Error: Into<Error> + Send + Debug,
diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs
new file mode 100644
index 00000000..bc9ba623
--- /dev/null
+++ b/lambda-runtime/src/diagnostic.rs
@@ -0,0 +1,141 @@
+use serde::{Deserialize, Serialize};
+use std::{any::type_name, borrow::Cow};
+
+use crate::{deserializer::DeserializeError, Error};
+
+/// Diagnostic information about an error.
+///
+/// `Diagnostic` is automatically derived for some common types,
+/// like boxed types that implement [`Error`][std::error::Error].
+///
+/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of
+/// the original error with [`std::any::type_name`] as a fallback, which may
+/// not be reliable for conditional error handling.
+/// You can define your own error container that implements `Into<Diagnostic>`
+/// if you need to handle errors based on error types.
+///
+/// Example:
+/// ```
+/// use lambda_runtime::{Diagnostic, Error, LambdaEvent};
+/// use std::borrow::Cow;
+///
+/// #[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<(), ErrorResponse> {
+///    // ... do something
+///    Ok(())
+/// }
+/// ```
+#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Diagnostic<'a> {
+    /// Error type.
+    ///
+    /// `error_type` is derived from the type name of the original error with
+    /// [`std::any::type_name`] as a fallback.
+    /// Please implement your own `Into<Diagnostic>` if you need more reliable
+    /// error types.
+    pub error_type: Cow<'a, str>,
+    /// Error message.
+    ///
+    /// `error_message` is the output from the [`Display`][std::fmt::Display]
+    /// implementation of the original error as a fallback.
+    pub error_message: Cow<'a, str>,
+}
+
+impl<'a> From<DeserializeError> for Diagnostic<'a> {
+    fn from(value: DeserializeError) -> Self {
+        Diagnostic {
+            error_type: type_name::<DeserializeError>().into(),
+            error_message: value.to_string().into(),
+        }
+    }
+}
+
+impl<'a> From<Error> for Diagnostic<'a> {
+    fn from(value: Error) -> Self {
+        Diagnostic {
+            error_type: type_name::<Error>().into(),
+            error_message: value.to_string().into(),
+        }
+    }
+}
+
+impl<'a, T> From<Box<T>> for Diagnostic<'a>
+where
+    T: std::error::Error,
+{
+    fn from(value: Box<T>) -> Self {
+        Diagnostic {
+            error_type: type_name::<T>().into(),
+            error_message: value.to_string().into(),
+        }
+    }
+}
+
+impl<'a> From<Box<dyn std::error::Error>> for Diagnostic<'a> {
+    fn from(value: Box<dyn std::error::Error>) -> Self {
+        Diagnostic {
+            error_type: type_name::<Box<dyn std::error::Error>>().into(),
+            error_message: value.to_string().into(),
+        }
+    }
+}
+
+impl<'a> From<std::convert::Infallible> for Diagnostic<'a> {
+    fn from(value: std::convert::Infallible) -> Self {
+        Diagnostic {
+            error_type: type_name::<std::convert::Infallible>().into(),
+            error_message: value.to_string().into(),
+        }
+    }
+}
+
+impl<'a> From<String> for Diagnostic<'a> {
+    fn from(value: String) -> Self {
+        Diagnostic {
+            error_type: type_name::<String>().into(),
+            error_message: value.into(),
+        }
+    }
+}
+
+impl<'a> From<&'static str> for Diagnostic<'a> {
+    fn from(value: &'static str) -> Self {
+        Diagnostic {
+            error_type: type_name::<&'static str>().into(),
+            error_message: value.into(),
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn round_trip_lambda_error() {
+        use serde_json::{json, Value};
+        let expected = json!({
+            "errorType": "InvalidEventDataError",
+            "errorMessage": "Error parsing event data.",
+        });
+
+        let actual = Diagnostic {
+            error_type: "InvalidEventDataError".into(),
+            error_message: "Error parsing event data.".into(),
+        };
+        let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic");
+        assert_eq!(expected, actual);
+    }
+}
diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs
index 266402cf..55942d0b 100644
--- a/lambda-runtime/src/layers/api_response.rs
+++ b/lambda-runtime/src/layers/api_response.rs
@@ -1,8 +1,9 @@
-use crate::requests::{EventCompletionRequest, IntoRequest};
-use crate::runtime::LambdaInvocation;
-use crate::types::Diagnostic;
-use crate::{deserializer, IntoFunctionResponse};
-use crate::{EventErrorRequest, LambdaEvent};
+use crate::{
+    deserializer,
+    requests::{EventCompletionRequest, IntoRequest},
+    runtime::LambdaInvocation,
+    Diagnostic, EventErrorRequest, IntoFunctionResponse, LambdaEvent,
+};
 use futures::ready;
 use futures::Stream;
 use lambda_runtime_api_client::{body::Body, BoxError};
diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs
index 9638df64..c3e68cc4 100644
--- a/lambda-runtime/src/lib.rs
+++ b/lambda-runtime/src/lib.rs
@@ -18,8 +18,12 @@ use tokio_stream::Stream;
 use tower::util::ServiceFn;
 pub use tower::{self, service_fn, Service};
 
+/// Diagnostic utilities to convert Rust types into Lambda Error types.
+pub mod diagnostic;
+pub use diagnostic::Diagnostic;
+
 mod deserializer;
-/// Tower middleware to be applied to runtime invocatinos.
+/// Tower middleware to be applied to runtime invocations.
 pub mod layers;
 mod requests;
 mod runtime;
@@ -35,9 +39,7 @@ mod types;
 
 use requests::EventErrorRequest;
 pub use runtime::{LambdaInvocation, Runtime};
-pub use types::{
-    Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse,
-};
+pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse};
 
 /// Error type that lambdas may result in
 pub type Error = lambda_runtime_api_client::BoxError;
diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs
index ec893710..de0835c7 100644
--- a/lambda-runtime/src/requests.rs
+++ b/lambda-runtime/src/requests.rs
@@ -1,5 +1,4 @@
-use crate::types::ToStreamErrorTrailer;
-use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse};
+use crate::{types::ToStreamErrorTrailer, Diagnostic, Error, FunctionResponse, IntoFunctionResponse};
 use bytes::Bytes;
 use http::header::CONTENT_TYPE;
 use http::{Method, Request, Uri};
diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs
index 5ded610c..92ba5f47 100644
--- a/lambda-runtime/src/runtime.rs
+++ b/lambda-runtime/src/runtime.rs
@@ -1,7 +1,7 @@
-use super::requests::{IntoRequest, NextEventRequest};
-use super::types::{invoke_request_id, Diagnostic, IntoFunctionResponse, LambdaEvent};
 use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService};
-use crate::{Config, Context};
+use crate::requests::{IntoRequest, NextEventRequest};
+use crate::types::{invoke_request_id, IntoFunctionResponse, LambdaEvent};
+use crate::{Config, Context, Diagnostic};
 use http_body_util::BodyExt;
 use lambda_runtime_api_client::BoxError;
 use lambda_runtime_api_client::Client as ApiClient;
@@ -252,8 +252,7 @@ mod endpoint_tests {
     use super::{incoming, wrap_handler};
     use crate::{
         requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest},
-        types::Diagnostic,
-        Config, Error, Runtime,
+        Config, Diagnostic, Error, Runtime,
     };
     use futures::future::BoxFuture;
     use http::{HeaderValue, StatusCode};
diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs
index b4f10f71..ee09978f 100644
--- a/lambda-runtime/src/types.rs
+++ b/lambda-runtime/src/types.rs
@@ -5,75 +5,12 @@ use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode};
 use lambda_runtime_api_client::body::Body;
 use serde::{Deserialize, Serialize};
 use std::{
-    borrow::Cow,
     collections::HashMap,
-    fmt::{Debug, Display},
+    fmt::Debug,
     time::{Duration, SystemTime},
 };
 use tokio_stream::Stream;
 
-/// Diagnostic information about an error.
-///
-/// `Diagnostic` is automatically derived for types that implement
-/// [`Display`][std::fmt::Display]; e.g., [`Error`][std::error::Error].
-///
-/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of
-/// the original error with [`std::any::type_name`] as a fallback, which may
-/// not be reliable for conditional error handling.
-/// You can define your own error container that implements `Into<Diagnostic>`
-/// if you need to handle errors based on error types.
-///
-/// Example:
-/// ```
-/// use lambda_runtime::{Diagnostic, Error, LambdaEvent};
-/// use std::borrow::Cow;
-///
-/// #[derive(Debug)]
-/// struct ErrorResponse(Error);
-///
-/// impl<'a> Into<Diagnostic<'a>> for ErrorResponse {
-///     fn into(self) -> Diagnostic<'a> {
-///         Diagnostic {
-///             error_type: Cow::Borrowed("MyError"),
-///             error_message: Cow::Owned(self.0.to_string()),
-///         }
-///     }
-/// }
-///
-/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> {
-///    // ... do something
-///    Ok(())
-/// }
-/// ```
-#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub struct Diagnostic<'a> {
-    /// Error type.
-    ///
-    /// `error_type` is derived from the type name of the original error with
-    /// [`std::any::type_name`] as a fallback.
-    /// Please implement your own `Into<Diagnostic>` if you need more reliable
-    /// error types.
-    pub error_type: Cow<'a, str>,
-    /// Error message.
-    ///
-    /// `error_message` is the output from the [`Display`][std::fmt::Display]
-    /// implementation of the original error as a fallback.
-    pub error_message: Cow<'a, str>,
-}
-
-impl<'a, T> From<T> for Diagnostic<'a>
-where
-    T: Display,
-{
-    fn from(value: T) -> Self {
-        Diagnostic {
-            error_type: Cow::Borrowed(std::any::type_name::<T>()),
-            error_message: Cow::Owned(format!("{value}")),
-        }
-    }
-}
-
 /// Client context sent by the AWS Mobile SDK.
 #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
 pub struct ClientContext {
@@ -342,22 +279,6 @@ mod test {
     use crate::Config;
     use std::sync::Arc;
 
-    #[test]
-    fn round_trip_lambda_error() {
-        use serde_json::{json, Value};
-        let expected = json!({
-            "errorType": "InvalidEventDataError",
-            "errorMessage": "Error parsing event data.",
-        });
-
-        let actual = Diagnostic {
-            error_type: Cow::Borrowed("InvalidEventDataError"),
-            error_message: Cow::Borrowed("Error parsing event data."),
-        };
-        let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic");
-        assert_eq!(expected, actual);
-    }
-
     #[test]
     fn context_with_expected_values_and_types_resolves() {
         let config = Arc::new(Config::default());


Test patch: