lambda_runtime/
diagnostic.rs

1use serde::{Deserialize, Serialize};
2use std::any::type_name;
3
4use crate::{deserializer::DeserializeError, Error};
5
6/// Diagnostic information about an error.
7///
8/// `Diagnostic` is automatically derived for some common types,
9/// like boxed types that implement [`Error`][std::error::Error].
10/// If you use an error type which comes from a external crate like anyhow,
11/// you need convert it to common types like `Box<dyn std::error::Error>`.
12/// See the examples for more details.
13///
14/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of
15/// the original error with [`std::any::type_name`] as a fallback, which may
16/// not be reliable for conditional error handling.
17///
18/// To get more descriptive [`error_type`][`Diagnostic::error_type`] fields, you can implement `From` for your error type.
19/// That gives you full control on what the `error_type` is.
20///
21/// Example:
22/// ```
23/// use lambda_runtime::{Diagnostic, Error, LambdaEvent};
24///
25/// #[derive(Debug)]
26/// struct ErrorResponse(&'static str);
27///
28/// impl From<ErrorResponse> for Diagnostic {
29///     fn from(error: ErrorResponse) -> Diagnostic {
30///         Diagnostic {
31///             error_type: "MyError".into(),
32///             error_message: error.0.to_string(),
33///         }
34///     }
35/// }
36///
37/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> {
38///    Err(ErrorResponse("this is an error response"))
39/// }
40/// ```
41#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct Diagnostic {
44    /// `error_type` is the type of exception or error returned by the function.
45    /// Use this field to categorize the different kinds of errors that your function
46    /// might experience.
47    ///
48    /// In standard implementations, `error_type` is derived from the type name of the original error with
49    /// [`std::any::type_name`], however this is not descriptive enough for an error type.
50    /// Implement your own `Into<Diagnostic>` to return a more descriptive error type.
51    pub error_type: String,
52    /// `error_message` is a string expression of the error.
53    /// In standard implementations, it's the output from the [`Display`][std::fmt::Display]
54    /// implementation of the original error.
55    pub error_message: String,
56}
57
58impl From<DeserializeError> for Diagnostic {
59    fn from(value: DeserializeError) -> Self {
60        Diagnostic {
61            error_type: type_name_of_val(&value),
62            error_message: value.to_string(),
63        }
64    }
65}
66
67impl From<Error> for Diagnostic {
68    fn from(value: Error) -> Self {
69        Diagnostic {
70            error_type: type_name_of_val(&value),
71            error_message: value.to_string(),
72        }
73    }
74}
75
76impl From<Box<dyn std::error::Error>> for Diagnostic {
77    fn from(value: Box<dyn std::error::Error>) -> Self {
78        Diagnostic {
79            error_type: type_name_of_val(&value),
80            error_message: value.to_string(),
81        }
82    }
83}
84
85impl From<std::convert::Infallible> for Diagnostic {
86    fn from(value: std::convert::Infallible) -> Self {
87        Diagnostic {
88            error_type: type_name_of_val(&value),
89            error_message: value.to_string(),
90        }
91    }
92}
93
94impl From<String> for Diagnostic {
95    fn from(value: String) -> Self {
96        Diagnostic {
97            error_type: type_name_of_val(&value),
98            error_message: value.to_string(),
99        }
100    }
101}
102
103impl From<&'static str> for Diagnostic {
104    fn from(value: &'static str) -> Self {
105        Diagnostic {
106            error_type: type_name_of_val(&value),
107            error_message: value.to_string(),
108        }
109    }
110}
111
112impl From<std::io::Error> for Diagnostic {
113    fn from(value: std::io::Error) -> Self {
114        Diagnostic {
115            error_type: type_name_of_val(&value),
116            error_message: value.to_string(),
117        }
118    }
119}
120
121#[cfg(feature = "anyhow")]
122#[cfg_attr(docsrs, doc(cfg(feature = "anyhow")))]
123impl From<anyhow::Error> for Diagnostic {
124    fn from(value: anyhow::Error) -> Diagnostic {
125        Diagnostic {
126            error_type: type_name_of_val(&value),
127            error_message: value.to_string(),
128        }
129    }
130}
131
132#[cfg(feature = "eyre")]
133#[cfg_attr(docsrs, doc(cfg(feature = "eyre")))]
134impl From<eyre::Report> for Diagnostic {
135    fn from(value: eyre::Report) -> Diagnostic {
136        Diagnostic {
137            error_type: type_name_of_val(&value),
138            error_message: value.to_string(),
139        }
140    }
141}
142
143#[cfg(feature = "miette")]
144#[cfg_attr(docsrs, doc(cfg(feature = "miette")))]
145impl From<miette::Report> for Diagnostic {
146    fn from(value: miette::Report) -> Diagnostic {
147        Diagnostic {
148            error_type: type_name_of_val(&value),
149            error_message: value.to_string(),
150        }
151    }
152}
153
154pub(crate) fn type_name_of_val<T>(_: T) -> String {
155    type_name::<T>().into()
156}
157
158#[cfg(test)]
159mod test {
160    use super::*;
161
162    #[test]
163    fn round_trip_lambda_error() {
164        use serde_json::{json, Value};
165        let expected = json!({
166            "errorType": "InvalidEventDataError",
167            "errorMessage": "Error parsing event data.",
168        });
169
170        let actual = Diagnostic {
171            error_type: "InvalidEventDataError".into(),
172            error_message: "Error parsing event data.".into(),
173        };
174        let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic");
175        assert_eq!(expected, actual);
176    }
177
178    #[cfg(feature = "anyhow")]
179    #[test]
180    fn test_anyhow_integration() {
181        use anyhow::Error as AnyhowError;
182        let error: AnyhowError = anyhow::anyhow!("anyhow error");
183        let diagnostic: Diagnostic = error.into();
184        assert_eq!(diagnostic.error_type, "&anyhow::Error");
185        assert_eq!(diagnostic.error_message, "anyhow error");
186    }
187
188    #[cfg(feature = "eyre")]
189    #[test]
190    fn test_eyre_integration() {
191        use eyre::Report;
192        let error: Report = eyre::eyre!("eyre error");
193        let diagnostic: Diagnostic = error.into();
194        assert_eq!(diagnostic.error_type, "&eyre::Report");
195        assert_eq!(diagnostic.error_message, "eyre error");
196    }
197
198    #[cfg(feature = "miette")]
199    #[test]
200    fn test_miette_integration() {
201        use miette::Report;
202        let error: Report = miette::miette!("miette error");
203        let diagnostic: Diagnostic = error.into();
204        assert_eq!(diagnostic.error_type, "&miette::eyreish::Report");
205        assert_eq!(diagnostic.error_message, "miette error");
206    }
207}