Skip to content

Commit 2360a05

Browse files
authored
Provide context when deserialization fails. (#640)
* Provide context when deserialization fails. Use serde_path_to_error to provide a little bit more information about the serde failure. Signed-off-by: David Calavera <david.calavera@gmail.com> * Include the error path in the message when the problem is not the root element "." Signed-off-by: David Calavera <david.calavera@gmail.com> * Cleanup formatting Signed-off-by: David Calavera <david.calavera@gmail.com> * Fix typo Signed-off-by: David Calavera <david.calavera@gmail.com> --------- Signed-off-by: David Calavera <david.calavera@gmail.com>
1 parent 393d644 commit 2360a05

File tree

4 files changed

+53
-10
lines changed

4 files changed

+53
-10
lines changed

lambda-runtime/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,4 @@ tracing = { version = "0.1.37", features = ["log"] }
4141
tower = { version = "0.4", features = ["util"] }
4242
tokio-stream = "0.1.2"
4343
lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" }
44+
serde_path_to_error = "0.1.11"

lambda-runtime/src/deserializer.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use std::{error::Error, fmt};
2+
3+
use serde::Deserialize;
4+
5+
use crate::{Context, LambdaEvent};
6+
7+
const ERROR_CONTEXT: &str = "failed to deserialize the incoming data into the function's payload type";
8+
9+
/// Event payload deserialization error.
10+
/// Returned when the data sent to the function cannot be deserialized
11+
/// into the type that the function receives.
12+
#[derive(Debug)]
13+
pub(crate) struct DeserializeError {
14+
inner: serde_path_to_error::Error<serde_json::Error>,
15+
}
16+
17+
impl fmt::Display for DeserializeError {
18+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19+
let path = self.inner.path().to_string();
20+
if path == "." {
21+
writeln!(f, "{ERROR_CONTEXT}: {}", self.inner)
22+
} else {
23+
writeln!(f, "{ERROR_CONTEXT}: [{path}] {}", self.inner)
24+
}
25+
}
26+
}
27+
28+
impl Error for DeserializeError {
29+
fn source(&self) -> Option<&(dyn Error + 'static)> {
30+
Some(&self.inner)
31+
}
32+
}
33+
34+
/// Deserialize the data sent to the function into the type that the function receives.
35+
pub(crate) fn deserialize<T>(body: &[u8], context: Context) -> Result<LambdaEvent<T>, DeserializeError>
36+
where
37+
T: for<'de> Deserialize<'de>,
38+
{
39+
let jd = &mut serde_json::Deserializer::from_slice(body);
40+
serde_path_to_error::deserialize(jd)
41+
.map(|payload| LambdaEvent::new(payload, context))
42+
.map_err(|inner| DeserializeError { inner })
43+
}

lambda-runtime/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ pub use tower::{self, service_fn, Service};
2828
use tower::{util::ServiceFn, ServiceExt};
2929
use tracing::{error, trace, Instrument};
3030

31+
mod deserializer;
3132
mod requests;
3233
#[cfg(test)]
3334
mod simulated;
@@ -149,8 +150,8 @@ where
149150
return Err(parts.status.to_string().into());
150151
}
151152

152-
let body = match serde_json::from_slice(&body) {
153-
Ok(body) => body,
153+
let lambda_event = match deserializer::deserialize(&body, ctx) {
154+
Ok(lambda_event) => lambda_event,
154155
Err(err) => {
155156
let req = build_event_error_request(request_id, err)?;
156157
client.call(req).await.expect("Unable to send response to Runtime APIs");
@@ -161,8 +162,7 @@ where
161162
let req = match handler.ready().await {
162163
Ok(handler) => {
163164
// Catches panics outside of a `Future`
164-
let task =
165-
panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx))));
165+
let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event)));
166166

167167
let task = match task {
168168
// Catches panics inside of the `Future`

lambda-runtime/src/streaming.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{
2-
build_event_error_request, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, IntoRequest,
3-
LambdaEvent, Runtime,
2+
build_event_error_request, deserializer, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest,
3+
IntoRequest, LambdaEvent, Runtime,
44
};
55
use bytes::Bytes;
66
use futures::FutureExt;
@@ -142,8 +142,8 @@ where
142142
return Err(parts.status.to_string().into());
143143
}
144144

145-
let body = match serde_json::from_slice(&body) {
146-
Ok(body) => body,
145+
let lambda_event = match deserializer::deserialize(&body, ctx) {
146+
Ok(lambda_event) => lambda_event,
147147
Err(err) => {
148148
let req = build_event_error_request(request_id, err)?;
149149
client.call(req).await.expect("Unable to send response to Runtime APIs");
@@ -154,8 +154,7 @@ where
154154
let req = match handler.ready().await {
155155
Ok(handler) => {
156156
// Catches panics outside of a `Future`
157-
let task =
158-
panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(LambdaEvent::new(body, ctx))));
157+
let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event)));
159158

160159
let task = match task {
161160
// Catches panics inside of the `Future`

0 commit comments

Comments
 (0)