From 4a3dbc99c5c6802c363f214472f864fc5c721f0d Mon Sep 17 00:00:00 2001 From: Adam Esterline Date: Tue, 2 Apr 2024 19:33:40 -0600 Subject: [PATCH] fix: support string or string slices when deserializing `IamPolicyStatement` (#854) `Action` and `Resource` allow both `string` and `[string]` as values. Support deserializing `IamPolicyStatement` with either of these values. fixes: https://github.com/awslabs/aws-lambda-rust-runtime/issues/853 --- lambda-events/src/custom_serde/mod.rs | 21 +++++++++++++++ lambda-events/src/event/apigw/mod.rs | 26 +++++++++++++++++-- ...uth-response-with-single-value-action.json | 19 ++++++++++++++ ...h-response-with-single-value-resource.json | 19 ++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 030cb5b3..9d68c8d3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -92,6 +92,27 @@ where Ok(opt.unwrap_or_default()) } +/// Deserializes `Vec`, from a JSON `string` or `[string]`. +#[cfg(any(feature = "apigw", test))] +pub(crate) fn deserialize_string_or_slice<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + #[derive(serde::Deserialize)] + #[serde(untagged)] + enum StringOrSlice { + String(String), + Slice(Vec), + } + + let string_or_slice = StringOrSlice::deserialize(deserializer)?; + + match string_or_slice { + StringOrSlice::Slice(slice) => Ok(slice), + StringOrSlice::String(s) => Ok(vec![s]), + } +} + #[cfg(test)] #[allow(deprecated)] mod test { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 1a9b1f1a..1777cf76 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,6 +1,6 @@ use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, - serialize_multi_value_headers, + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, deserialize_string_or_slice, http_method, + serialize_headers, serialize_multi_value_headers, }; use crate::encodings::Body; use http::{HeaderMap, Method}; @@ -737,11 +737,13 @@ pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(rename_all = "camelCase")] pub struct IamPolicyStatement { #[serde(rename = "Action")] + #[serde(deserialize_with = "deserialize_string_or_slice")] pub action: Vec, #[serde(default)] #[serde(rename = "Effect")] pub effect: Option, #[serde(rename = "Resource")] + #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, } @@ -832,6 +834,26 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_single_value_action() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-single-value-action.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_single_value_resource() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-single-value-resource.json"); + let parsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_request() { diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json new file mode 100644 index 00000000..e656caaa --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-action.json @@ -0,0 +1,19 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Resource": ["arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"] + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json new file mode 100644 index 00000000..af96bb17 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-single-value-resource.json @@ -0,0 +1,19 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["execute-api:Invoke"], + "Effect": "Allow", + "Resource": "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +}