From e29dc519d9b90745c2661f36896b4d4529b6479a Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Fri, 3 Jan 2025 08:21:45 +0000 Subject: [PATCH 1/2] fix: serialize `AlbTargetGroupRequest::query_string_parameters` value to string fix #954 --- lambda-events/src/event/alb/mod.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 3b4ce9d5..eec1ad1b 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -17,6 +17,7 @@ pub struct AlbTargetGroupRequest { #[serde(default)] pub path: Option, #[serde(default)] + #[serde(serialize_with = "query_map::serde::aws_api_gateway_v1::serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap, @@ -70,6 +71,7 @@ pub struct AlbTargetGroupResponse { #[cfg(test)] mod test { use super::*; + use serde_json::Value; #[test] #[cfg(feature = "alb")] @@ -91,6 +93,19 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "alb")] + fn ensure_alb_lambda_target_request_query_string_parameter_value_is_string() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-request-headers-only.json"); + let parsed: AlbTargetGroupRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: Value = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!( + reparsed["queryStringParameters"]["key"], + Value::String("hello".to_string()) + ); + } + #[test] #[cfg(feature = "alb")] fn example_alb_lambda_target_response() { From f7aa5160eeb49c9ab89bf0edaf684c792ef4a471 Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Wed, 8 Jan 2025 02:53:47 +0000 Subject: [PATCH 2/2] fix: serialize AlbTargetGroupRequest::query_string_parameters with the last value of each key and prevent unnecessary mem alloc. #954 --- lambda-events/src/custom_serde/mod.rs | 5 ++ .../custom_serde/query_string_parameters.rs | 63 +++++++++++++++++++ lambda-events/src/event/alb/mod.rs | 5 +- 3 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 lambda-events/src/custom_serde/query_string_parameters.rs diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 729dee3d..eb44a113 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -33,6 +33,11 @@ pub(crate) mod float_unix_epoch; #[cfg(any(feature = "alb", feature = "apigw"))] pub(crate) mod http_method; +#[cfg(feature = "alb")] +mod query_string_parameters; +#[cfg(feature = "alb")] +pub(crate) use self::query_string_parameters::*; + pub(crate) fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, diff --git a/lambda-events/src/custom_serde/query_string_parameters.rs b/lambda-events/src/custom_serde/query_string_parameters.rs new file mode 100644 index 00000000..a7144eda --- /dev/null +++ b/lambda-events/src/custom_serde/query_string_parameters.rs @@ -0,0 +1,63 @@ +use query_map::QueryMap; +use serde::{ser::SerializeMap, Serializer}; +use std::collections::HashMap; + +/// Serializes `QueryMap`, converting value from `Vec` to `String` using the last value. +pub fn serialize_query_string_parameters(value: &QueryMap, serializer: S) -> Result +where + S: Serializer, +{ + let mut query_string_parameters = HashMap::new(); + + if let Some((mut last_key, mut last_value)) = value.iter().next() { + // insert the last value for each key + value.iter().for_each(|(k, v)| { + if k != last_key { + query_string_parameters.insert(last_key, last_value); + last_key = k; + } + last_value = v; + }); + // insert the last pair + query_string_parameters.insert(last_key, last_value); + } + + let mut map = serializer.serialize_map(Some(query_string_parameters.len()))?; + for (k, v) in &query_string_parameters { + map.serialize_entry(k, v)?; + } + map.end() +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Serialize; + use serde_json::Value; + + #[test] + fn test_serialize_query_string_parameters() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_query_string_parameters")] + pub v: QueryMap, + } + + fn s(value: &str) -> String { + value.to_string() + } + + let query = QueryMap::from(HashMap::from([ + (s("key1"), vec![s("value1"), s("value2"), s("value3")]), + (s("key2"), vec![s("value4")]), + (s("key3"), vec![s("value5"), s("value6")]), + ])); + + let serialized = serde_json::to_string(&Test { v: query }).unwrap(); + let parsed: Value = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(parsed["v"]["key1"], Value::String("value3".to_string())); + assert_eq!(parsed["v"]["key2"], Value::String("value4".to_string())); + assert_eq!(parsed["v"]["key3"], Value::String("value6".to_string())); + } +} diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index eec1ad1b..03d21ccd 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,6 +1,7 @@ use crate::{ custom_serde::{ - deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, serialize_query_string_parameters, }, encodings::Body, }; @@ -17,7 +18,7 @@ pub struct AlbTargetGroupRequest { #[serde(default)] pub path: Option, #[serde(default)] - #[serde(serialize_with = "query_map::serde::aws_api_gateway_v1::serialize_query_string_parameters")] + #[serde(serialize_with = "serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap,