From d652415319061474b64f098337097c97de133588 Mon Sep 17 00:00:00 2001 From: softprops Date: Sun, 3 Mar 2019 15:12:33 -0500 Subject: [PATCH 1/2] expose functions for deserializing http types from raw event sources --- CHANGELOG.md | 3 +- lambda-http/src/request.rs | 83 ++++++++++++++++++++++++++++++-------- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ab1ca26a..8f914433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # next (unreleased) -- **New**: `lambda_http::RequestExt` crate now exposes mock helper methods under `cfg(test)` builds to facilitate straightforward unit testability of handlers +- **New**: The `lambda_http` crate now exposes mock helper methods for `RequestExt` under `cfg(test)` builds to facilitate straight forward unit testability of handlers. +- **New**: The `lambda_http` crate now exposes two new functions for deserializing requests from text and raw IO: `lambda_http::request::{from_str,from_reader}`. # 0.2.0 diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index a1c9fd27..86d9e47b 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -1,4 +1,4 @@ -//! ALB andAPI Gateway request types. +//! ALB and API Gateway request types. //! //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) @@ -12,7 +12,7 @@ use http::{ }; use serde::de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor}; use serde_derive::Deserialize; -use serde_json::Value; +use serde_json::{error::Error as JsonError, Value}; use crate::{ body::Body, @@ -322,12 +322,55 @@ impl<'a> From> for HttpRequest { } } +/// Deserializes a Request from an IO stream of JSON. +/// +/// # Example +/// +/// ```rust,no_run +/// use lambda_http::request::from_reader; +/// use std::fs::File; +/// use std::error::Error; +/// +/// fn main() -> Result<(), Box> { +/// let request = from_reader( +/// File::open("path/to/request.json")? +/// )?; +/// Ok(println!("{:#?}", request)) +/// } +/// ``` +pub fn from_reader(rdr: R) -> Result +where + R: std::io::Read, +{ + serde_json::from_reader(rdr).map(LambdaRequest::into) +} + +/// Deserializes a Request from a string of JSON text. +/// +/// # Example +/// +/// ```rust,no_run +/// use lambda_http::request::from_str; +/// use std::fs::File; +/// use std::error::Error; +/// +/// fn main() -> Result<(), Box> { +/// let request = from_str( +/// r#"{ ...raw json here... }"# +/// )?; +/// Ok(println!("{:#?}", request)) +/// } +/// ``` +pub fn from_str(s: &str) -> Result { + serde_json::from_str(s).map(LambdaRequest::into) +} + #[cfg(test)] mod tests { use super::*; use crate::RequestExt; use serde_json; - use std::collections::HashMap; + use std::{collections::HashMap, fs::File}; #[test] fn requests_convert() { @@ -345,12 +388,21 @@ mod tests { assert_eq!(expected.method(), actual.method()); } + #[test] + fn deserializes_apigw_request_events_from_readables() { + // from the docs + // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request + // note: file paths are relative to the directory of the crate at runtime + let result = from_reader(File::open("tests/data/apigw_proxy_request.json").expect("expected file")); + assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); + } + #[test] fn deserializes_apigw_request_events() { // from the docs // https://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-api-gateway-request let input = include_str!("../tests/data/apigw_proxy_request.json"); - let result = serde_json::from_str::>(&input); + let result = from_str(input); assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); } @@ -359,7 +411,7 @@ mod tests { // from the docs // https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers let input = include_str!("../tests/data/alb_request.json"); - let result = serde_json::from_str::>(&input); + let result = from_str(input); assert!(result.is_ok(), format!("event was not parsed as expected {:?}", result)); } @@ -368,19 +420,18 @@ mod tests { // from docs // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json"); - let result = serde_json::from_str::>(&input); + let result = from_str(input); assert!( result.is_ok(), format!("event is was not parsed as expected {:?}", result) ); - let apigw = result.unwrap(); - assert!(!apigw.query_string_parameters.is_empty()); - assert!(!apigw.multi_value_query_string_parameters.is_empty()); - let actual = HttpRequest::from(apigw); + let unwrapped = result.unwrap(); + + assert!(!unwrapped.query_string_parameters().is_empty()); // test RequestExt#query_string_parameters does the right thing assert_eq!( - actual.query_string_parameters().get_all("multivalueName"), + unwrapped.query_string_parameters().get_all("multivalueName"), Some(vec!["you", "me"]) ); } @@ -390,19 +441,17 @@ mod tests { // from docs // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format let input = include_str!("../tests/data/alb_multi_value_request.json"); - let result = serde_json::from_str::>(&input); + let result = from_str(input); assert!( result.is_ok(), format!("event is was not parsed as expected {:?}", result) ); - let apigw = result.unwrap(); - assert!(!apigw.query_string_parameters.is_empty()); - assert!(!apigw.multi_value_query_string_parameters.is_empty()); - let actual = HttpRequest::from(apigw); + let unwrapped = result.unwrap(); + assert!(!unwrapped.query_string_parameters().is_empty()); // test RequestExt#query_string_parameters does the right thing assert_eq!( - actual.query_string_parameters().get_all("myKey"), + unwrapped.query_string_parameters().get_all("myKey"), Some(vec!["val1", "val2"]) ); } From e85456097cb359aa8198e49a102bc9aacdd31f9a Mon Sep 17 00:00:00 2001 From: softprops Date: Sun, 3 Mar 2019 15:19:35 -0500 Subject: [PATCH 2/2] use std::io::Read for qualification --- lambda-http/src/request.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 86d9e47b..ab2ddfef 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -3,8 +3,6 @@ //! Typically these are exposed via the `request_context` //! request extension method provided by [lambda_http::RequestExt](../trait.RequestExt.html) //! -use std::{borrow::Cow, collections::HashMap, fmt, mem}; - use http::{ self, header::{HeaderName, HeaderValue, HOST}, @@ -13,6 +11,7 @@ use http::{ use serde::de::{Deserialize, Deserializer, Error as DeError, MapAccess, Visitor}; use serde_derive::Deserialize; use serde_json::{error::Error as JsonError, Value}; +use std::{borrow::Cow, collections::HashMap, fmt, io::Read, mem}; use crate::{ body::Body, @@ -340,7 +339,7 @@ impl<'a> From> for HttpRequest { /// ``` pub fn from_reader(rdr: R) -> Result where - R: std::io::Read, + R: Read, { serde_json::from_reader(rdr).map(LambdaRequest::into) }