Skip to content

Commit 2a81fd5

Browse files
committed
refactor(lambda-events): align VPC Lattice with library conventions
- Add #[non_exhaustive] to all VPC Lattice structs - Change status_code from u16 to i64 for consistency with ALB/API Gateway - Move comma-separated header serde into custom_serde/headers.rs - Fix test imports and function naming
1 parent 9e80c20 commit 2a81fd5

7 files changed

Lines changed: 95 additions & 252 deletions

File tree

lambda-events/src/custom_serde/headers.rs

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ where
3434
map.end()
3535
}
3636

37+
/// Serialize a HeaderMap with multiple values per header combined as comma-separated strings.
38+
/// Used by VPC Lattice V1 which expects multi-value headers as "value1, value2".
39+
#[cfg(feature = "vpc_lattice")]
40+
pub(crate) fn serialize_comma_separated_headers<S>(headers: &HeaderMap, serializer: S) -> Result<S::Ok, S::Error>
41+
where
42+
S: Serializer,
43+
{
44+
let mut map = serializer.serialize_map(Some(headers.keys_len()))?;
45+
for key in headers.keys() {
46+
let values: Vec<&str> = headers
47+
.get_all(key)
48+
.iter()
49+
.filter_map(|v| v.to_str().ok())
50+
.collect();
51+
if !values.is_empty() {
52+
let combined_value = values.join(", ");
53+
map.serialize_entry(key.as_str(), &combined_value)?;
54+
}
55+
}
56+
map.end()
57+
}
58+
3759
#[derive(serde::Deserialize)]
3860
#[serde(untagged)]
3961
enum OneOrMore<'a> {
@@ -44,6 +66,7 @@ enum OneOrMore<'a> {
4466

4567
struct HeaderMapVisitor {
4668
is_human_readable: bool,
69+
split_comma_separated: bool,
4770
}
4871

4972
impl<'de> Visitor<'de> for HeaderMapVisitor {
@@ -97,17 +120,25 @@ impl<'de> Visitor<'de> for HeaderMapVisitor {
97120
.map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?;
98121
match val {
99122
OneOrMore::One(val) => {
100-
let val = val
101-
.parse()
102-
.map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?;
103-
map.insert(key, val);
104-
}
105-
OneOrMore::Strings(arr) => {
106-
for val in arr {
123+
if self.split_comma_separated && val.contains(',') {
124+
split_and_append_header(&mut map, &key, &val, &self)?;
125+
} else {
107126
let val = val
108127
.parse()
109128
.map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?;
110-
map.append(&key, val);
129+
map.insert(key, val);
130+
}
131+
}
132+
OneOrMore::Strings(arr) => {
133+
for val in arr {
134+
if self.split_comma_separated && val.contains(',') {
135+
split_and_append_header(&mut map, &key, &val, &self)?;
136+
} else {
137+
let val = val
138+
.parse()
139+
.map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?;
140+
map.append(&key, val);
141+
}
111142
}
112143
}
113144
OneOrMore::Bytes(arr) => {
@@ -124,13 +155,51 @@ impl<'de> Visitor<'de> for HeaderMapVisitor {
124155
}
125156
}
126157

158+
fn split_and_append_header<E>(
159+
map: &mut HeaderMap,
160+
key: &HeaderName,
161+
value: &str,
162+
visitor: &HeaderMapVisitor,
163+
) -> Result<(), E>
164+
where
165+
E: DeError,
166+
{
167+
for split_val in value.split(',') {
168+
let trimmed_val = split_val.trim();
169+
if !trimmed_val.is_empty() {
170+
let header_val = trimmed_val
171+
.parse()
172+
.map_err(|_| de::Error::invalid_value(Unexpected::Str(trimmed_val), visitor))?;
173+
map.append(key, header_val);
174+
}
175+
}
176+
Ok(())
177+
}
178+
127179
/// Implementation detail.
128180
pub(crate) fn deserialize_headers<'de, D>(de: D) -> Result<HeaderMap, D::Error>
129181
where
130182
D: Deserializer<'de>,
131183
{
132184
let is_human_readable = de.is_human_readable();
133-
de.deserialize_option(HeaderMapVisitor { is_human_readable })
185+
de.deserialize_option(HeaderMapVisitor {
186+
is_human_readable,
187+
split_comma_separated: false,
188+
})
189+
}
190+
191+
/// Deserialize headers, splitting comma-separated values into multiple header values.
192+
/// Used by VPC Lattice V1 which sends multi-value headers as "value1, value2".
193+
#[cfg(feature = "vpc_lattice")]
194+
pub(crate) fn deserialize_comma_separated_headers<'de, D>(de: D) -> Result<HeaderMap, D::Error>
195+
where
196+
D: Deserializer<'de>,
197+
{
198+
let is_human_readable = de.is_human_readable();
199+
de.deserialize_option(HeaderMapVisitor {
200+
is_human_readable,
201+
split_comma_separated: true,
202+
})
134203
}
135204

136205
#[cfg(test)]

lambda-events/src/custom_serde/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,24 @@ pub type CodeBuildNumber = f32;
1616
feature = "apigw",
1717
feature = "s3",
1818
feature = "iot",
19-
feature = "lambda_function_urls"
19+
feature = "lambda_function_urls",
20+
feature = "vpc_lattice"
2021
))]
2122
mod headers;
2223
#[cfg(any(
2324
feature = "alb",
2425
feature = "apigw",
2526
feature = "s3",
2627
feature = "iot",
27-
feature = "lambda_function_urls"
28+
feature = "lambda_function_urls",
29+
feature = "vpc_lattice"
2830
))]
2931
pub(crate) use self::headers::*;
3032

3133
#[cfg(feature = "dynamodb")]
3234
pub(crate) mod float_unix_epoch;
3335

34-
#[cfg(any(feature = "alb", feature = "apigw"))]
36+
#[cfg(any(feature = "alb", feature = "apigw", feature = "vpc_lattice"))]
3537
pub(crate) mod http_method;
3638

3739
#[cfg(feature = "alb")]
@@ -89,6 +91,7 @@ where
8991
feature = "code_commit",
9092
feature = "cognito",
9193
feature = "sns",
94+
feature = "vpc_lattice",
9295
test
9396
))]
9497
pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result<bool, D::Error>

lambda-events/src/event/vpc_lattice/common.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use serde_json::Value;
77

88
/// `VpcLatticeResponse` configures the response to be returned
99
/// by VPC Lattice (both V1 and V2) for the request
10+
#[non_exhaustive]
1011
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
1112
#[serde(rename_all = "camelCase")]
1213
pub struct VpcLatticeResponse {
@@ -16,7 +17,8 @@ pub struct VpcLatticeResponse {
1617
pub is_base64_encoded: bool,
1718

1819
/// The HTTP status code for the request
19-
pub status_code: u16,
20+
// i64 for consistency with other event types (e.g. AlbTargetGroupResponse, ApiGatewayProxyResponse)
21+
pub status_code: i64,
2022

2123
/// The HTTP status description (optional)
2224
#[serde(default)]
@@ -48,7 +50,7 @@ mod test {
4850

4951
#[test]
5052
#[cfg(feature = "vpc_lattice")]
51-
fn example_alb_lambda_target_response() {
53+
fn example_vpc_lattice_response() {
5254
let data = include_bytes!("../../fixtures/example-vpc-lattice-response.json");
5355
let parsed: VpcLatticeResponse = serde_json::from_slice(data).unwrap();
5456
let output: String = serde_json::to_string(&parsed).unwrap();
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
mod common;
2-
mod serialization_comma_separated_headers;
32
mod v1;
43
mod v2;
54

65
// re-export types
76
pub use self::{common::*, v1::*, v2::*};
8-
9-
// helper code
10-
pub(crate) use self::serialization_comma_separated_headers::*;

0 commit comments

Comments
 (0)