Skip to content

Commit abef8a4

Browse files
committed
Report error when we cannot deserialize the payload.
Instead of panic, capture the error, and report it to the Lambda api. This is more friendly to operate. Signed-off-by: David Calavera <david.calavera@gmail.com>
1 parent fd2ea23 commit abef8a4

File tree

3 files changed

+80
-64
lines changed

3 files changed

+80
-64
lines changed

lambda-runtime/src/lib.rs

+46-43
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

+15-3
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

+19-18
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)