|
1 | 1 | use std::collections::HashMap; |
2 | 2 | use std::io::Cursor; |
3 | 3 |
|
4 | | -use serde::Deserialize; |
| 4 | +use serde::{Deserialize, Deserializer}; |
5 | 5 |
|
6 | 6 | use crate::appsec::processor::InvocationPayload; |
7 | | -use crate::lifecycle::invocation::triggers::{body::Body, lowercase_key}; |
| 7 | +use crate::lifecycle::invocation::triggers::body::Body; |
8 | 8 |
|
9 | 9 | /// The expected payload of a response. This is different from trigger to trigger. |
10 | 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
@@ -50,9 +50,9 @@ impl Default for ExpectedResponseFormat { |
50 | 50 | #[serde(rename_all = "camelCase")] |
51 | 51 | struct ApiGatewayResponse { |
52 | 52 | status_code: i64, |
53 | | - #[serde(deserialize_with = "lowercase_key", default)] |
| 53 | + #[serde(deserialize_with = "nullable_lowercase_key", default)] |
54 | 54 | headers: HashMap<String, String>, |
55 | | - #[serde(deserialize_with = "lowercase_key", default)] |
| 55 | + #[serde(deserialize_with = "nullable_lowercase_key", default)] |
56 | 56 | multi_value_headers: HashMap<String, Vec<String>>, |
57 | 57 | #[serde(flatten)] |
58 | 58 | body: Body, |
@@ -101,3 +101,39 @@ impl InvocationPayload for RawPayload { |
101 | 101 | Some(Box::new(Cursor::new(&self.data))) |
102 | 102 | } |
103 | 103 | } |
| 104 | + |
| 105 | +fn nullable_lowercase_key<'de, D, V>(deserializer: D) -> Result<HashMap<String, V>, D::Error> |
| 106 | +where |
| 107 | + D: Deserializer<'de>, |
| 108 | + V: Deserialize<'de>, |
| 109 | +{ |
| 110 | + let Some(map) = Option::<HashMap<String, V>>::deserialize(deserializer)? else { |
| 111 | + return Ok(HashMap::default()); |
| 112 | + }; |
| 113 | + Ok(map |
| 114 | + .into_iter() |
| 115 | + .map(|(key, value)| (key.to_lowercase(), value)) |
| 116 | + .collect()) |
| 117 | +} |
| 118 | + |
| 119 | +#[cfg(test)] |
| 120 | +mod test { |
| 121 | + use super::*; |
| 122 | + |
| 123 | + #[test] |
| 124 | + fn test_null_fields_in_apigw_response() { |
| 125 | + let response = r#"{ |
| 126 | + "statusCode": 0, |
| 127 | + "headers": null, |
| 128 | + "multiValueHeaders": null, |
| 129 | + "body": null |
| 130 | + }"#; |
| 131 | + let response = ExpectedResponseFormat::ApiGatewayResponse |
| 132 | + .parse(response.as_bytes()) |
| 133 | + .expect("response should have parsed cleanly") |
| 134 | + .expect("response should have been Some"); |
| 135 | + assert!(response.response_body().is_none()); |
| 136 | + assert!(response.response_headers_no_cookies().is_empty()); |
| 137 | + assert_eq!(response.response_status_code(), Some(0)); |
| 138 | + } |
| 139 | +} |
0 commit comments