From 8da501ecee99e04cae5536b2bf5f217ef556337d Mon Sep 17 00:00:00 2001
From: Harold Sun <sunhua@amazon.com>
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 <sunhua@amazon.com>
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<Value> {
     Json(json!({ "msg": "I am GET /" }))
@@ -36,12 +33,12 @@ async fn post_foo_name(Path(name): Path<String>) -> Json<Value> {
     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 <sunhua@amazon.com>
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<Value> {
     Json(json!({ "msg": "I am GET /" }))
@@ -33,12 +36,12 @@ async fn post_foo_name(Path(name): Path<String>) -> Json<Value> {
     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 <sunhua@amazon.com>
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 <sunhua@amazon.com>
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()
+        );
     }
 }
