Skip to content

Commit 756dfc4

Browse files
authored
Report error when we cannot deserialize the payload. (#520)
Instead of panic, capture the error, and report it to the Lambda api. This is more friendly to operate. Signed-off-by: David Calavera <[email protected]> Signed-off-by: David Calavera <[email protected]>
1 parent fd2ea23 commit 756dfc4

File tree

3 files changed

+80
-64
lines changed

3 files changed

+80
-64
lines changed

lambda-runtime/src/lib.rs

Lines changed: 46 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,20 @@
77
//! Create a type that conforms to the [`tower::Service`] trait. This type can
88
//! then be passed to the the `lambda_runtime::run` function, which launches
99
//! and runs the Lambda runtime.
10-
use hyper::client::{connect::Connection, HttpConnector};
10+
use hyper::{
11+
client::{connect::Connection, HttpConnector},
12+
http::Request,
13+
Body,
14+
};
1115
use lambda_runtime_api_client::Client;
1216
use serde::{Deserialize, Serialize};
13-
use std::{convert::TryFrom, env, fmt, future::Future, panic};
17+
use std::{
18+
convert::TryFrom,
19+
env,
20+
fmt::{self, Debug, Display},
21+
future::Future,
22+
panic,
23+
};
1424
use tokio::io::{AsyncRead, AsyncWrite};
1525
use tokio_stream::{Stream, StreamExt};
1626
pub use tower::{self, service_fn, Service};
@@ -24,7 +34,6 @@ mod simulated;
2434
mod types;
2535

2636
use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest};
27-
use types::Diagnostic;
2837
pub use types::{Context, LambdaEvent};
2938

3039
/// Error type that lambdas may result in
@@ -121,12 +130,20 @@ where
121130

122131
let ctx: Context = Context::try_from(parts.headers)?;
123132
let ctx: Context = ctx.with_config(config);
124-
let body = serde_json::from_slice(&body)?;
133+
let request_id = &ctx.request_id.clone();
125134

126135
let xray_trace_id = &ctx.xray_trace_id.clone();
127136
env::set_var("_X_AMZN_TRACE_ID", xray_trace_id);
128137

129-
let request_id = &ctx.request_id.clone();
138+
let body = match serde_json::from_slice(&body) {
139+
Ok(body) => body,
140+
Err(err) => {
141+
let req = build_event_error_request(request_id, err)?;
142+
client.call(req).await.expect("Unable to send response to Runtime APIs");
143+
return Ok(());
144+
}
145+
};
146+
130147
let req = match handler.ready().await {
131148
Ok(handler) => {
132149
let task =
@@ -141,48 +158,23 @@ where
141158
}
142159
.into_req()
143160
}
144-
Err(err) => {
145-
error!("{:?}", err); // logs the error in CloudWatch
146-
EventErrorRequest {
147-
request_id,
148-
diagnostic: Diagnostic {
149-
error_type: type_name_of_val(&err).to_owned(),
150-
error_message: format!("{}", err), // returns the error to the caller via Lambda API
151-
},
152-
}
153-
.into_req()
154-
}
161+
Err(err) => build_event_error_request(request_id, err),
155162
},
156163
Err(err) => {
157164
error!("{:?}", err);
158-
EventErrorRequest {
159-
request_id,
160-
diagnostic: Diagnostic {
161-
error_type: type_name_of_val(&err).to_owned(),
162-
error_message: if let Some(msg) = err.downcast_ref::<&str>() {
163-
format!("Lambda panicked: {}", msg)
164-
} else {
165-
"Lambda panicked".to_string()
166-
},
167-
},
168-
}
169-
.into_req()
165+
let error_type = type_name_of_val(&err);
166+
let msg = if let Some(msg) = err.downcast_ref::<&str>() {
167+
format!("Lambda panicked: {}", msg)
168+
} else {
169+
"Lambda panicked".to_string()
170+
};
171+
EventErrorRequest::new(request_id, error_type, &msg).into_req()
170172
}
171173
}
172174
}
173-
Err(err) => {
174-
error!("{:?}", err); // logs the error in CloudWatch
175-
EventErrorRequest {
176-
request_id,
177-
diagnostic: Diagnostic {
178-
error_type: type_name_of_val(&err).to_owned(),
179-
error_message: format!("{}", err), // returns the error to the caller via Lambda API
180-
},
181-
}
182-
.into_req()
183-
}
184-
};
185-
let req = req?;
175+
Err(err) => build_event_error_request(request_id, err),
176+
}?;
177+
186178
client.call(req).await.expect("Unable to send response to Runtime APIs");
187179
}
188180
Ok(())
@@ -247,6 +239,17 @@ fn type_name_of_val<T>(_: T) -> &'static str {
247239
std::any::type_name::<T>()
248240
}
249241

242+
fn build_event_error_request<T>(request_id: &str, err: T) -> Result<Request<Body>, Error>
243+
where
244+
T: Display + Debug,
245+
{
246+
error!("{:?}", err); // logs the error in CloudWatch
247+
let error_type = type_name_of_val(&err);
248+
let msg = format!("{}", err);
249+
250+
EventErrorRequest::new(request_id, error_type, &msg).into_req()
251+
}
252+
250253
#[cfg(test)]
251254
mod endpoint_tests {
252255
use crate::{
@@ -431,8 +434,8 @@ mod endpoint_tests {
431434
let req = EventErrorRequest {
432435
request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9",
433436
diagnostic: Diagnostic {
434-
error_type: "InvalidEventDataError".to_string(),
435-
error_message: "Error parsing event data".to_string(),
437+
error_type: "InvalidEventDataError",
438+
error_message: "Error parsing event data",
436439
},
437440
};
438441
let req = req.into_req()?;

lambda-runtime/src/requests.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,19 @@ fn test_event_completion_request() {
104104
// /runtime/invocation/{AwsRequestId}/error
105105
pub(crate) struct EventErrorRequest<'a> {
106106
pub(crate) request_id: &'a str,
107-
pub(crate) diagnostic: Diagnostic,
107+
pub(crate) diagnostic: Diagnostic<'a>,
108+
}
109+
110+
impl<'a> EventErrorRequest<'a> {
111+
pub(crate) fn new(request_id: &'a str, error_type: &'a str, error_message: &'a str) -> EventErrorRequest<'a> {
112+
EventErrorRequest {
113+
request_id,
114+
diagnostic: Diagnostic {
115+
error_type,
116+
error_message,
117+
},
118+
}
119+
}
108120
}
109121

110122
impl<'a> IntoRequest for EventErrorRequest<'a> {
@@ -128,8 +140,8 @@ fn test_event_error_request() {
128140
let req = EventErrorRequest {
129141
request_id: "id",
130142
diagnostic: Diagnostic {
131-
error_type: "InvalidEventDataError".to_string(),
132-
error_message: "Error parsing event data".to_string(),
143+
error_type: "InvalidEventDataError",
144+
error_message: "Error parsing event data",
133145
},
134146
};
135147
let req = req.into_req().unwrap();

lambda-runtime/src/types.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,24 +5,9 @@ use std::{collections::HashMap, convert::TryFrom};
55

66
#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)]
77
#[serde(rename_all = "camelCase")]
8-
pub(crate) struct Diagnostic {
9-
pub(crate) error_type: String,
10-
pub(crate) error_message: String,
11-
}
12-
13-
#[test]
14-
fn round_trip_lambda_error() -> Result<(), Error> {
15-
use serde_json::{json, Value};
16-
let expected = json!({
17-
"errorType": "InvalidEventDataError",
18-
"errorMessage": "Error parsing event data.",
19-
});
20-
21-
let actual: Diagnostic = serde_json::from_value(expected.clone())?;
22-
let actual: Value = serde_json::to_value(actual)?;
23-
assert_eq!(expected, actual);
24-
25-
Ok(())
8+
pub(crate) struct Diagnostic<'a> {
9+
pub(crate) error_type: &'a str,
10+
pub(crate) error_message: &'a str,
2611
}
2712

2813
/// The request ID, which identifies the request that triggered the function invocation. This header
@@ -191,6 +176,22 @@ impl<T> LambdaEvent<T> {
191176
mod test {
192177
use super::*;
193178

179+
#[test]
180+
fn round_trip_lambda_error() {
181+
use serde_json::{json, Value};
182+
let expected = json!({
183+
"errorType": "InvalidEventDataError",
184+
"errorMessage": "Error parsing event data.",
185+
});
186+
187+
let actual = Diagnostic {
188+
error_type: "InvalidEventDataError",
189+
error_message: "Error parsing event data.",
190+
};
191+
let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic");
192+
assert_eq!(expected, actual);
193+
}
194+
194195
#[test]
195196
fn context_with_expected_values_and_types_resolves() {
196197
let mut headers = HeaderMap::new();

0 commit comments

Comments
 (0)