From 8da501ecee99e04cae5536b2bf5f217ef556337d Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 28 Jan 2024 21:46:36 +0800 Subject: [PATCH 1/5] Support serialize UTF-8 String in HTTP Header Values --- lambda-events/src/custom_serde/headers.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 9cb89e40..bde8850e 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -13,7 +13,7 @@ where for key in headers.keys() { let mut map_values = Vec::new(); for value in headers.get_all(key) { - map_values.push(value.to_str().map_err(S::Error::custom)?) + map_values.push(String::from_utf8(value.as_bytes().to_vec()).map_err(S::Error::custom)?) } map.serialize_entry(key.as_str(), &map_values)?; } @@ -27,8 +27,8 @@ where { let mut map = serializer.serialize_map(Some(headers.keys_len()))?; for key in headers.keys() { - let map_value = headers[key].to_str().map_err(S::Error::custom)?; - map.serialize_entry(key.as_str(), map_value)?; + let map_value = String::from_utf8(headers[key].as_bytes().to_vec()).map_err(S::Error::custom)?; + map.serialize_entry(key.as_str(), &map_value)?; } map.end() } From 77ad45a39fa8194bd3deec820ec795fa9a1f928a Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 28 Jan 2024 22:02:48 +0800 Subject: [PATCH 2/5] Fix issues in http-axum example --- examples/http-axum/src/main.rs | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index cc3600a2..1e22ace3 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,19 +6,16 @@ //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. -use std::env::set_var; use axum::http::StatusCode; -use lambda_http::{ - run, - Error, -}; use axum::{ extract::Path, response::Json, - Router, routing::{get, post}, + Router, }; -use serde_json::{Value, json}; +use lambda_http::{run, Error}; +use serde_json::{json, Value}; +use std::env::set_var; async fn root() -> Json { Json(json!({ "msg": "I am GET /" })) @@ -36,12 +33,12 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } -/// Example on how to return status codes and data from a Axum function -async fn health_check() -> (StatusCode, &str) { +/// Example on how to return status codes and data from an Axum function +async fn health_check() -> (StatusCode, &'static str) { let healthy = false; - match health { - true => {(StatusCode::OK, "Healthy!")}, - false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} + match healthy { + true => (StatusCode::OK, "Healthy!"), + false => (StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!"), } } @@ -51,7 +48,7 @@ async fn main() -> Result<(), Error> { // Remove if you want the first section of the url to be the stage name of the API Gateway // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); - + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) From b36316dbb7c9b8ace4bfe53b740d6ac4daa17e0f Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 29 Jan 2024 00:25:29 +0800 Subject: [PATCH 3/5] Revert "Fix issues in http-axum example" This reverts commit 77ad45a39fa8194bd3deec820ec795fa9a1f928a. --- examples/http-axum/src/main.rs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 1e22ace3..cc3600a2 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,16 +6,19 @@ //! implementation to the Lambda runtime to run as a Lambda function. By using Axum instead //! of a basic `tower::Service` you get web framework niceties like routing, request component //! extraction, validation, etc. +use std::env::set_var; use axum::http::StatusCode; +use lambda_http::{ + run, + Error, +}; use axum::{ extract::Path, response::Json, - routing::{get, post}, Router, + routing::{get, post}, }; -use lambda_http::{run, Error}; -use serde_json::{json, Value}; -use std::env::set_var; +use serde_json::{Value, json}; async fn root() -> Json { Json(json!({ "msg": "I am GET /" })) @@ -33,12 +36,12 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } -/// Example on how to return status codes and data from an Axum function -async fn health_check() -> (StatusCode, &'static str) { +/// Example on how to return status codes and data from a Axum function +async fn health_check() -> (StatusCode, &str) { let healthy = false; - match healthy { - true => (StatusCode::OK, "Healthy!"), - false => (StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!"), + match health { + true => {(StatusCode::OK, "Healthy!")}, + false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} } } @@ -48,7 +51,7 @@ async fn main() -> Result<(), Error> { // Remove if you want the first section of the url to be the stage name of the API Gateway // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); - + // required to enable CloudWatch error logging by the runtime tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) From 8f2db593236feaedc673d4c34076fa48ac7f2206 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 29 Jan 2024 07:56:28 +0800 Subject: [PATCH 4/5] Add a unit test --- lambda-events/src/custom_serde/headers.rs | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index bde8850e..0573064b 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -187,4 +187,28 @@ mod tests { let decoded: Test = serde_json::from_value(data).unwrap(); assert!(decoded.headers.is_empty()); } + + #[test] + fn test_serialize_utf8_headers() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + headers: HeaderMap, + } + + let content_disposition = + "inline; filename=\"Schillers schönste Szenenanweisungen -Kabale und Liebe.mp4.avif\""; + let data = serde_json::json!({ + "headers": { + "Content-Disposition": content_disposition + } + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + + let recoded = serde_json::to_value(decoded).unwrap(); + let decoded: Test = serde_json::from_value(recoded).unwrap(); + assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + } } From c3519c0acf6baaf72c03659a1923c9591184b4cf Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 29 Jan 2024 08:17:44 +0800 Subject: [PATCH 5/5] Add a unit test to cover both 'serialize_headers' and 'serialize_multi_value_headers' --- lambda-events/src/custom_serde/headers.rs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 0573064b..2ef3050e 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -192,9 +192,12 @@ mod tests { fn test_serialize_utf8_headers() { #[derive(Deserialize, Serialize)] struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_multi_value_headers")] - headers: HeaderMap, + pub multi_value_headers: HeaderMap, } let content_disposition = @@ -202,13 +205,24 @@ mod tests { let data = serde_json::json!({ "headers": { "Content-Disposition": content_disposition + }, + "multi_value_headers": { + "Content-Disposition": content_disposition } }); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + assert_eq!( + content_disposition, + decoded.multi_value_headers.get("Content-Disposition").unwrap() + ); let recoded = serde_json::to_value(decoded).unwrap(); let decoded: Test = serde_json::from_value(recoded).unwrap(); assert_eq!(content_disposition, decoded.headers.get("Content-Disposition").unwrap()); + assert_eq!( + content_disposition, + decoded.multi_value_headers.get("Content-Disposition").unwrap() + ); } }