diff --git a/README.md b/README.md index 44ec1983..31ba399b 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ pip3 install cargo-lambda See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). -### Your first function +## Your first function To create your first function, run Cargo Lambda with the [subcommand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: @@ -71,6 +71,61 @@ async fn func(event: LambdaEvent) -> Result { } ``` +## Understanding Lambda errors + +when a function invocation fails, AWS Lambda expects you to return an object that can be serialized into JSON structure with the error information. This structure is represented in the following example: + +```json +{ + "error_type": "the type of error raised", + "error_message": "a string description of the error" +} +``` + +The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the converstion of several general errors types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. + +### Implement your own Diagnostic + +To get more descriptive `error_type` fields, you can implement `From` for your error type. That gives you full control on what the `error_type` is: + +```rust +use lambda_runtime::{Diagnostic, Error, LambdaEvent}; + +#[derive(Debug)] +struct ErrorResponse(&'static str); + +impl From for Diagnostic { + fn from(error: ErrorResponse) -> Diagnostic { + Diagnostic { + error_type: "MyErrorType".into(), + error_message: error.0.to_string(), + } + } +} + +async fn handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { + Err(ErrorResponse("this is an error response")) +} +``` + +We recommend you to use the [thiserror crate](https://crates.io/crates/thiserror) to declare your errors. You can see an example on how to integrate `thiserror` with the Runtime's diagnostics in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-thiserror) + +### Anyhow, Eyre, and Miette + +Popular error crates like Anyhow, Eyre, and Miette provide their own error types that encapsulate other errors. There is no direct transformation of those errors into `Diagnostic`, but we provide feature flags for each one of those crates to help you integrate them with your Lambda functions. + +If you enable the features `anyhow`, `eyre`, or `miette` in the `lambda_runtime` dependency of your package. The error types provided by those crates can have blanket transformations into `Diagnostic`. These features expose an `From for Diagnostic` implementation that transforms those error types into a `Diagnostic`. This is an example that transforms an `anyhow::Error` into a `Diagnostic`: + +```rust +use lambda_runtime::{Diagnostic, LambdaEvent}; + +async fn handler(_event: LambdaEvent) -> Result<(), Diagnostic> { + Err(anyhow::anyhow!("this is an error").into()) +} +``` + +You can see more examples on how to use these error crates in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-error-crates-integration). + ## Building and deploying your Lambda functions If you already have Cargo Lambda installed in your machine, run the next command to build your function: diff --git a/examples/basic-error-anyhow/Cargo.toml b/examples/basic-error-anyhow/Cargo.toml deleted file mode 100644 index a0ff62db..00000000 --- a/examples/basic-error-anyhow/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "basic-error-anyhow" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1" -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1" -tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/src/main.rs b/examples/basic-error-anyhow/src/main.rs deleted file mode 100644 index cbca84fd..00000000 --- a/examples/basic-error-anyhow/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::bail; -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::Deserialize; - -#[derive(Deserialize)] -struct Request {} - -/// Return anyhow::Result in the main body for the Lambda function. -async fn function_handler(_event: LambdaEvent) -> anyhow::Result<()> { - bail!("This is an error message"); -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_runtime::run(service_fn(|event: LambdaEvent| async move { - function_handler(event) - .await - .map_err(Into::>::into) - })) - .await -} diff --git a/examples/basic-error-anyhow/.gitignore b/examples/basic-error-error-crates-integration/.gitignore similarity index 100% rename from examples/basic-error-anyhow/.gitignore rename to examples/basic-error-error-crates-integration/.gitignore diff --git a/examples/basic-error-error-crates-integration/Cargo.toml b/examples/basic-error-error-crates-integration/Cargo.toml new file mode 100644 index 00000000..741ec713 --- /dev/null +++ b/examples/basic-error-error-crates-integration/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "basic-error-error-crates-integration" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +eyre = "0.6.12" +lambda_runtime = { path = "../../lambda-runtime", features = ["anyhow", "eyre", "miette"] } +miette = "7.2.0" +serde = "1" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/README.md b/examples/basic-error-error-crates-integration/README.md similarity index 54% rename from examples/basic-error-anyhow/README.md rename to examples/basic-error-error-crates-integration/README.md index b659c283..2e46c55d 100644 --- a/examples/basic-error-anyhow/README.md +++ b/examples/basic-error-error-crates-integration/README.md @@ -1,6 +1,8 @@ -# AWS Lambda Function Error Handling With `anyhow` Crate Example +# AWS Lambda Function Error Handling with several popular error crates. -This example shows how to use external error types like `anyhow::Error`. +This example shows how to use external error types like `anyhow::Error`, `eyre::Report`, and `miette::Report`. + +To use the integrations with these crates, you need to enable to respective feature flag in the runtime which provides the implemetation of `into_diagnostic` for specific error types provided by these crates. ## Build & Deploy diff --git a/examples/basic-error-error-crates-integration/src/main.rs b/examples/basic-error-error-crates-integration/src/main.rs new file mode 100644 index 00000000..f4048584 --- /dev/null +++ b/examples/basic-error-error-crates-integration/src/main.rs @@ -0,0 +1,44 @@ +use lambda_runtime::{run, service_fn, Diagnostic, Error, LambdaEvent}; +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum ErrorType { + Anyhow, + Eyre, + Miette, +} + +#[derive(Deserialize)] +struct Request { + error_type: ErrorType, +} + +fn anyhow_error() -> anyhow::Result<()> { + anyhow::bail!("This is an error message from Anyhow"); +} + +fn eyre_error() -> eyre::Result<()> { + eyre::bail!("This is an error message from Eyre"); +} + +fn miette_error() -> miette::Result<()> { + miette::bail!("This is an error message from Miette"); +} + +/// Transform an anyhow::Error, eyre::Report, or miette::Report into a lambda_runtime::Diagnostic. +/// It does it by enabling the feature `anyhow`, `eyre` or `miette` in the runtime dependency. +/// Those features enable the implementation of `From for Diagnostic` +/// for `anyhow::Error`, `eyre::Report`, and `miette::Report`. +async fn function_handler(event: LambdaEvent) -> Result<(), Diagnostic> { + match event.payload.error_type { + ErrorType::Anyhow => anyhow_error().map_err(|e| e.into()), + ErrorType::Eyre => eyre_error().map_err(|e| e.into()), + ErrorType::Miette => miette_error().map_err(|e| e.into()), + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(function_handler)).await +} diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-thiserror/.gitignore similarity index 100% rename from examples/basic-error-diagnostic/.gitignore rename to examples/basic-error-thiserror/.gitignore diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-thiserror/Cargo.toml similarity index 94% rename from examples/basic-error-diagnostic/Cargo.toml rename to examples/basic-error-thiserror/Cargo.toml index b81ef730..d7c7d725 100644 --- a/examples/basic-error-diagnostic/Cargo.toml +++ b/examples/basic-error-thiserror/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic-error-diagnostic" +name = "basic-error-thiserror" version = "0.1.0" edition = "2021" diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-thiserror/README.md similarity index 100% rename from examples/basic-error-diagnostic/README.md rename to examples/basic-error-thiserror/README.md diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-thiserror/src/main.rs similarity index 90% rename from examples/basic-error-diagnostic/src/main.rs rename to examples/basic-error-thiserror/src/main.rs index 11f68d4b..403309bf 100644 --- a/examples/basic-error-diagnostic/src/main.rs +++ b/examples/basic-error-thiserror/src/main.rs @@ -13,8 +13,8 @@ pub enum ExecutionError { Unexpected(String), } -impl<'a> From for Diagnostic<'a> { - fn from(value: ExecutionError) -> Diagnostic<'a> { +impl From for Diagnostic { + fn from(value: ExecutionError) -> Diagnostic { let (error_type, error_message) = match value { ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()), ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()), diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1165084b..fc822b95 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -22,7 +22,11 @@ apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] -tracing = ["lambda_runtime/tracing"] +tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities +opentelemetry = ["lambda_runtime/opentelemetry"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["lambda_runtime/anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["lambda_runtime/eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["lambda_runtime/miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] base64 = { workspace = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 90e59867..233d6992 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -194,7 +194,7 @@ where S: Service, S::Future: Send + 'a, R: IntoResponse, - E: std::fmt::Debug + for<'b> Into>, + E: std::fmt::Debug + Into, { lambda_runtime::run(Adapter::from(handler)).await } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index ad3471d3..a93408b4 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -20,7 +20,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), where S: Service, Error = E>, S::Future: Send + 'a, - E: Debug + for<'b> Into>, + E: Debug + Into, B: Body + Unpin + Send + 'static, B::Data: Into + Send, B::Error: Into + Send + Debug, diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9e56d05b..371b32f7 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -15,13 +15,18 @@ readme = "../README.md" [features] default = ["tracing"] -tracing = ["lambda_runtime_api_client/tracing"] -opentelemetry = ["opentelemetry-semantic-conventions"] +tracing = ["lambda_runtime_api_client/tracing"] # enables access to the Tracing utilities +opentelemetry = ["opentelemetry-semantic-conventions"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["dep:anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["dep:eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["dep:miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] +anyhow = { version = "1.0.86", optional = true } async-stream = "0.3" base64 = { workspace = true } bytes = { workspace = true } +eyre = { version = "0.6.12", optional = true } futures = { workspace = true } http = { workspace = true } http-body = { workspace = true } @@ -35,6 +40,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.14", optional = true } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index 9a7230a7..c03ce284 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{any::type_name, borrow::Cow}; +use std::any::type_name; use crate::{deserializer::DeserializeError, Error}; @@ -14,114 +14,144 @@ use crate::{deserializer::DeserializeError, 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` -/// if you need to handle errors based on error types. +/// +/// To get more descriptive [`error_type`][`Diagnostic::error_type`] fields, you can implement `From` for your error type. +/// That gives you full control on what the `error_type` is. /// /// Example: /// ``` /// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; -/// use std::borrow::Cow; /// /// #[derive(Debug)] -/// struct ErrorResponse(Error); +/// struct ErrorResponse(&'static str); /// -/// impl<'a> Into> for ErrorResponse { -/// fn into(self) -> Diagnostic<'a> { +/// impl From for Diagnostic { +/// fn from(error: ErrorResponse) -> Diagnostic { /// Diagnostic { /// error_type: "MyError".into(), -/// error_message: self.0.to_string().into(), +/// error_message: error.0.to_string(), /// } /// } /// } /// /// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { -/// // ... do something -/// Ok(()) +/// Err(ErrorResponse("this is an error response")) /// } /// ``` #[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` if you need more reliable - /// error types. - pub error_type: Cow<'a, str>, - /// Error message. +pub struct Diagnostic { + /// `error_type` is the type of exception or error returned by the function. + /// Use this field to categorize the different kinds of errors that your function + /// might experience. /// - /// `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>, + /// In standard implementations, `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`], however this is not descriptive enough for an error type. + /// Implement your own `Into` to return a more descriptive error type. + pub error_type: String, + /// `error_message` is a string expression of the error. + /// In standard implementations, it's the output from the [`Display`][std::fmt::Display] + /// implementation of the original error. + pub error_message: String, } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: DeserializeError) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: Error) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a, T> From> for Diagnostic<'a> -where - T: std::error::Error, -{ - fn from(value: Box) -> Self { +impl From> for Diagnostic { + fn from(value: Box) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From> for Diagnostic<'a> { - fn from(value: Box) -> Self { +impl From for Diagnostic { + fn from(value: std::convert::Infallible) -> Self { Diagnostic { - error_type: type_name::>().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: std::convert::Infallible) -> Self { +impl From for Diagnostic { + fn from(value: String) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: String) -> Self { +impl From<&'static str> for Diagnostic { + fn from(value: &'static str) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From<&'static str> for Diagnostic<'a> { - fn from(value: &'static str) -> Self { +impl From for Diagnostic { + fn from(value: std::io::Error) -> Self { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "anyhow")] +impl From for Diagnostic { + fn from(value: anyhow::Error) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "eyre")] +impl From for Diagnostic { + fn from(value: eyre::Report) -> Diagnostic { Diagnostic { - error_type: type_name::<&'static str>().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } +#[cfg(feature = "miette")] +impl From for Diagnostic { + fn from(value: miette::Report) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +pub(crate) fn type_name_of_val(_: T) -> String { + type_name::().into() +} + #[cfg(test)] mod test { use super::*; @@ -141,4 +171,34 @@ mod test { let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); assert_eq!(expected, actual); } + + #[cfg(feature = "anyhow")] + #[test] + fn test_anyhow_integration() { + use anyhow::Error as AnyhowError; + let error: AnyhowError = anyhow::anyhow!("anyhow error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&anyhow::Error"); + assert_eq!(diagnostic.error_message, "anyhow error"); + } + + #[cfg(feature = "eyre")] + #[test] + fn test_eyre_integration() { + use eyre::Report; + let error: Report = eyre::eyre!("eyre error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&eyre::Report"); + assert_eq!(diagnostic.error_message, "eyre error"); + } + + #[cfg(feature = "miette")] + #[test] + fn test_miette_integration() { + use miette::Report; + let error: Report = miette::miette!("miette error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&miette::eyreish::Report"); + assert_eq!(diagnostic.error_message, "miette error"); + } } diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 7c963f68..e744cde1 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -51,8 +51,7 @@ impl - Service +impl Service for RuntimeApiResponseService< S, EventPayload, @@ -63,7 +62,7 @@ impl<'a, S, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI StreamError, > where - S: Service, Response = Response, Error = Diagnostic<'a>>, + S: Service, Response = Response, Error = Diagnostic>, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -74,7 +73,7 @@ where type Response = http::Request; type Error = BoxError; type Future = - RuntimeApiResponseFuture<'a, S::Future, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>; + RuntimeApiResponseFuture; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { self.inner @@ -120,21 +119,21 @@ where } } -fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, BoxError> +fn build_event_error_request(request_id: &str, err: T) -> Result, BoxError> where - T: Into> + Debug, + T: Into + Debug, { error!(error = ?err, "Request payload deserialization into LambdaEvent failed. The handler will not be called. Log at TRACE level to see the payload."); EventErrorRequest::new(request_id, err).into_req() } #[pin_project(project = RuntimeApiResponseFutureProj)] -pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> { +pub enum RuntimeApiResponseFuture { Future( #[pin] F, String, PhantomData<( - &'a (), + (), Response, BufferedResponse, StreamingResponse, @@ -145,10 +144,10 @@ pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingRe Ready(Option, BoxError>>), } -impl<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> Future - for RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +impl Future + for RuntimeApiResponseFuture where - F: Future>>, + F: Future>, Response: IntoFunctionResponse, BufferedResponse: Serialize, StreamingResponse: Stream> + Unpin + Send + 'static, diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 036b747b..c76348ac 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -1,9 +1,7 @@ -use crate::{Diagnostic, LambdaEvent}; +use crate::{diagnostic::type_name_of_val, Diagnostic, LambdaEvent}; use futures::{future::CatchUnwind, FutureExt}; use pin_project::pin_project; -use std::{ - any::Any, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task, -}; +use std::{any::Any, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task}; use tower::Service; use tracing::error; @@ -33,9 +31,9 @@ impl<'a, S, Payload> Service> for CatchPanicService<'a, S> where S: Service>, S::Future: 'a, - S::Error: Into> + Debug, + S::Error: Into + Debug, { - type Error = Diagnostic<'a>; + type Error = Diagnostic; type Response = S::Response; type Future = CatchPanicFuture<'a, S::Future>; @@ -71,9 +69,9 @@ pub enum CatchPanicFuture<'a, F> { impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> where F: Future>, - E: Into> + Debug, + E: Into + Debug, { - type Output = Result>; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { use task::Poll; @@ -98,20 +96,15 @@ where } impl<'a, F> CatchPanicFuture<'a, F> { - fn build_panic_diagnostic(err: &Box) -> Diagnostic<'a> { - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { + fn build_panic_diagnostic(err: &Box) -> Diagnostic { + let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; Diagnostic { - error_type: Cow::Borrowed(error_type), - error_message: Cow::Owned(msg), + error_type: type_name_of_val(err), + error_message, } } } - -fn type_name_of_val(_: T) -> &'static str { - std::any::type_name::() -} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c3e68cc4..76d0562a 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -112,7 +112,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service, Response = R>, F::Future: Future>, - F::Error: for<'a> Into> + fmt::Debug, + F::Error: Into + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 0e2eb59a..535f1e8e 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -158,11 +158,11 @@ fn test_event_completion_request() { // /runtime/invocation/{AwsRequestId}/error pub(crate) struct EventErrorRequest<'a> { pub(crate) request_id: &'a str, - pub(crate) diagnostic: Diagnostic<'a>, + pub(crate) diagnostic: Diagnostic, } impl<'a> EventErrorRequest<'a> { - pub(crate) fn new(request_id: &'a str, diagnostic: impl Into>) -> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, diagnostic: impl Into) -> EventErrorRequest<'a> { EventErrorRequest { request_id, diagnostic: diagnostic.into(), @@ -224,8 +224,8 @@ mod tests { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), - error_message: std::borrow::Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 56c8efad..1c676480 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -72,7 +72,7 @@ impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -207,7 +207,7 @@ fn wrap_handler<'a, F, EventPayload, Response, BufferedResponse, StreamingRespon where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -257,7 +257,7 @@ mod endpoint_tests { use httpmock::prelude::*; use lambda_runtime_api_client::Client; - use std::{borrow::Cow, env, sync::Arc}; + use std::{env, sync::Arc}; use tokio_stream::StreamExt; #[tokio::test] @@ -324,8 +324,8 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { let diagnostic = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }; let body = serde_json::to_string(&diagnostic)?;