From 7c932659c74744d97f8a02de749e4fbf88008057 Mon Sep 17 00:00:00 2001 From: Thitat Auareesuksakul Date: Tue, 16 May 2023 00:22:59 +0900 Subject: [PATCH 001/211] Tag `apigw` struct fields with proper `serde(skip_serializing_if)` (#654) --- lambda-events/src/event/apigw/mod.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 80063911..88b44fec 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -81,6 +81,7 @@ where pub account_id: Option, #[serde(default)] pub resource_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub operation_name: Option, #[serde(default)] pub stage: Option, @@ -125,6 +126,7 @@ pub struct ApiGatewayV2httpRequest { pub raw_path: Option, #[serde(default)] pub raw_query_string: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub cookies: Option>, #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] @@ -133,14 +135,17 @@ pub struct ApiGatewayV2httpRequest { default, deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" )] + #[serde(skip_serializing_if = "QueryMap::is_empty")] pub query_string_parameters: QueryMap, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub path_parameters: HashMap, pub request_context: ApiGatewayV2httpRequestContext, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, #[serde(default)] pub is_base64_encoded: bool, @@ -163,6 +168,7 @@ where #[serde(default)] pub request_id: Option, #[serde(bound = "", default)] + #[serde(skip_serializing_if = "Option::is_none")] pub authorizer: Option>, /// The API Gateway HTTP API Id #[serde(default)] @@ -176,6 +182,7 @@ where pub time: Option, pub time_epoch: i64, pub http: ApiGatewayV2httpRequestContextHttpDescription, + #[serde(skip_serializing_if = "Option::is_none")] pub authentication: Option, } @@ -187,11 +194,14 @@ where T1: DeserializeOwned, T1: Serialize, { + #[serde(skip_serializing_if = "Option::is_none")] pub jwt: Option, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] #[serde(bound = "")] + #[serde(skip_serializing_if = "HashMap::is_empty")] pub lambda: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub iam: Option, } @@ -202,6 +212,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub claims: HashMap, + #[serde(skip_serializing_if = "Option::is_none")] pub scopes: Option>, } @@ -215,6 +226,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { pub account_id: Option, #[serde(default)] pub caller_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] pub cognito_identity: Option, #[serde(default)] pub principal_org_id: Option, From b19a79ef64e6b5682cedcd22b82a33b49eadbfb3 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:23 -0700 Subject: [PATCH 002/211] Fix support for APIGW console requests (#657) Test requests from the APIGW console don't include time epoch. This change makes that field to be deserialized with a default value in those cases. Signed-off-by: David Calavera --- lambda-events/src/event/apigw/mod.rs | 12 ++++ .../example-apigw-console-request.json | 57 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-console-request.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 88b44fec..196bff86 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -107,6 +107,7 @@ where pub http_method: Method, #[serde(default)] pub request_time: Option, + #[serde(default)] pub request_time_epoch: i64, /// The API Gateway rest API Id #[serde(default)] @@ -180,6 +181,7 @@ where pub domain_prefix: Option, #[serde(default)] pub time: Option, + #[serde(default)] pub time_epoch: i64, pub http: ApiGatewayV2httpRequestContextHttpDescription, #[serde(skip_serializing_if = "Option::is_none")] @@ -920,4 +922,14 @@ mod test { let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_console_request() { + let data = include_bytes!("../../fixtures/example-apigw-console-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-apigw-console-request.json b/lambda-events/src/fixtures/example-apigw-console-request.json new file mode 100644 index 00000000..e56f4cd7 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-console-request.json @@ -0,0 +1,57 @@ +{ + "body": "{\"test\":\"body\"}", + "resource": "/{proxy+}", + "path": "/path/to/resource", + "httpMethod": "POST", + "queryStringParameters": { + "foo": "bar" + }, + "pathParameters": { + "proxy": "path/to/resource" + }, + "stageVariables": { + "baz": "qux" + }, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Cache-Control": "max-age=0", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Host": "1234567890.execute-api.{dns_suffix}", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Custom User Agent String", + "Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==", + "X-Forwarded-For": "127.0.0.1, 127.0.0.2", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "resourceId": "123456", + "stage": "prod", + "requestId": "c6af9ac6-7b61-11e6-9a41-93e8deadbeef", + "identity": { + "cognitoIdentityPoolId": null, + "accountId": null, + "cognitoIdentityId": null, + "caller": null, + "apiKey": null, + "sourceIp": "127.0.0.1", + "cognitoAuthenticationType": null, + "cognitoAuthenticationProvider": null, + "userArn": null, + "userAgent": "Custom User Agent String", + "user": null + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "apiId": "1234567890" + } +} \ No newline at end of file From dbdf822c16914c53f787f3cdba13589ebc2fc93e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:36 -0700 Subject: [PATCH 003/211] Fix doc about curl command (#653) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 157c1c98..12dbf523 100644 --- a/README.md +++ b/README.md @@ -358,7 +358,7 @@ An simpler alternative is to cURL the following endpoint based on the address an ```bash curl -v -X POST \ - 'http://127.0.0.1:9001/lambda-url/' \ + 'http://127.0.0.1:9001/lambda-url//' \ -H 'content-type: application/json' \ -d '{ "command": "hi" }' ``` From 007f91565138f94e5efbfc84368a28adf2803cfd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 21 May 2023 20:45:50 -0700 Subject: [PATCH 004/211] Add missing RawHttpPath extension to WebSockets (#656) Signed-off-by: David Calavera --- lambda-http/src/request.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index fe9b5fb1..5ed3effe 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -277,7 +277,8 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< .get(http::header::HOST) .and_then(|s| s.to_str().ok()) .or(ag.request_context.domain_name.as_deref()); - let path = apigw_path_with_stage(&ag.request_context.stage, &ag.path.unwrap_or_default()); + let raw_path = ag.path.unwrap_or_default(); + let path = apigw_path_with_stage(&ag.request_context.stage, &raw_path); let builder = http::Request::builder() .uri(build_request_uri( @@ -286,6 +287,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< host, Some((&ag.multi_value_query_string_parameters, &ag.query_string_parameters)), )) + .extension(RawHttpPath(raw_path)) // multi-valued query string parameters are always a super // set of singly valued query string parameters, // when present, multi-valued query string parameters are preferred From a7329a452b34dc8e4927ba30605c3d8716d1db2f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 28 May 2023 14:34:23 -0700 Subject: [PATCH 005/211] Improvements in event compilation (#659) * Make compiling chrono conditional It's not necessary for all event types. Signed-off-by: David Calavera * Make compiling query_map conditional. It's not used in all events. Signed-off-by: David Calavera * Test individual features - Convert package to 2021 edition. - Format imports. Signed-off-by: David Calavera * Remove old idioms So people don't have to learn 2015 idioms like `extern crate` and implicit macro imports. Signed-off-by: David Calavera * Bump crate version Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 9 + Makefile | 44 +++ lambda-events/Cargo.toml | 57 ++- .../src/custom_serde/codebuild_time.rs | 11 +- .../src/custom_serde/float_unix_epoch.rs | 6 +- lambda-events/src/custom_serde/headers.rs | 13 +- lambda-events/src/custom_serde/http_method.rs | 11 +- lambda-events/src/custom_serde/mod.rs | 277 +------------ .../src/{encodings.rs => encodings/http.rs} | 121 +----- lambda-events/src/encodings/mod.rs | 37 ++ lambda-events/src/encodings/time.rs | 363 ++++++++++++++++++ lambda-events/src/event/activemq/mod.rs | 6 +- lambda-events/src/event/alb/mod.rs | 13 +- lambda-events/src/event/apigw/mod.rs | 4 +- lambda-events/src/event/appsync/mod.rs | 7 +- lambda-events/src/event/autoscaling/mod.rs | 7 +- lambda-events/src/event/chime_bot/mod.rs | 1 + lambda-events/src/event/clientvpn/mod.rs | 4 +- .../src/event/cloudwatch_events/cloudtrail.rs | 3 +- .../src/event/cloudwatch_events/codedeploy.rs | 3 +- .../event/cloudwatch_events/codepipeline.rs | 3 +- .../src/event/cloudwatch_events/ec2.rs | 3 +- .../src/event/cloudwatch_events/emr.rs | 3 +- .../src/event/cloudwatch_events/gamelift.rs | 3 +- .../src/event/cloudwatch_events/glue.rs | 3 +- .../src/event/cloudwatch_events/health.rs | 4 +- .../src/event/cloudwatch_events/kms.rs | 3 +- .../src/event/cloudwatch_events/macie.rs | 4 +- .../src/event/cloudwatch_events/mod.rs | 2 +- .../src/event/cloudwatch_events/opsworks.rs | 3 +- .../src/event/cloudwatch_events/signin.rs | 3 +- .../src/event/cloudwatch_events/sms.rs | 3 +- .../src/event/cloudwatch_events/ssm.rs | 4 +- .../src/event/cloudwatch_events/tag.rs | 3 +- .../event/cloudwatch_events/trustedadvisor.rs | 4 +- .../src/event/cloudwatch_logs/mod.rs | 2 +- lambda-events/src/event/code_commit/mod.rs | 3 +- lambda-events/src/event/codebuild/mod.rs | 6 +- lambda-events/src/event/codedeploy/mod.rs | 3 +- .../src/event/codepipeline_cloudwatch/mod.rs | 3 +- .../src/event/codepipeline_job/mod.rs | 4 +- lambda-events/src/event/cognito/mod.rs | 7 +- lambda-events/src/event/config/mod.rs | 4 +- lambda-events/src/event/connect/mod.rs | 6 +- .../src/event/dynamodb/attributes.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 6 +- lambda-events/src/event/ecr_scan/mod.rs | 4 +- lambda-events/src/event/firehose/mod.rs | 9 +- lambda-events/src/event/iam/mod.rs | 2 + lambda-events/src/event/iot/mod.rs | 5 +- lambda-events/src/event/iot_1_click/mod.rs | 7 +- lambda-events/src/event/iot_button/mod.rs | 4 +- lambda-events/src/event/iot_deprecated/mod.rs | 1 + lambda-events/src/event/kafka/mod.rs | 6 +- lambda-events/src/event/kinesis/analytics.rs | 1 + lambda-events/src/event/kinesis/event.rs | 3 +- .../src/event/lambda_function_urls/mod.rs | 4 +- lambda-events/src/event/lex/mod.rs | 6 +- lambda-events/src/event/mod.rs | 2 - lambda-events/src/event/rabbitmq/mod.rs | 7 +- lambda-events/src/event/s3/batch_job.rs | 2 + lambda-events/src/event/s3/event.rs | 6 +- lambda-events/src/event/s3/object_lambda.rs | 7 +- lambda-events/src/event/ses/mod.rs | 3 +- lambda-events/src/event/sns/mod.rs | 5 +- lambda-events/src/event/sqs/mod.rs | 5 +- lambda-events/src/event/streams/mod.rs | 2 + lambda-events/src/lib.rs | 26 +- lambda-events/src/time_window.rs | 3 +- lambda-http/Cargo.toml | 2 +- 70 files changed, 653 insertions(+), 560 deletions(-) rename lambda-events/src/{encodings.rs => encodings/http.rs} (75%) create mode 100644 lambda-events/src/encodings/mod.rs create mode 100644 lambda-events/src/encodings/time.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index dbf9a0ae..3a56e597 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -26,3 +26,12 @@ jobs: with: package: aws_lambda_events toolchain: ${{ matrix.toolchain}} + check-event-features: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Test individual event features + run: make check-event-features diff --git a/Makefile b/Makefile index cb00545c..544d08b7 100644 --- a/Makefile +++ b/Makefile @@ -60,3 +60,47 @@ invoke-integration-api-%: curl -X POST -d '{"command": "hello"}' $(API_URL)/trait/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2/post curl -X POST -d '{"command": "hello"}' $(API_URL)/al2-trait/post + +# Test individual event features to ensure optional dependencies +# are correctly loaded when all default features are disabled. +check-event-features: + cargo test --package aws_lambda_events --no-default-features --features activemq + cargo test --package aws_lambda_events --no-default-features --features alb + cargo test --package aws_lambda_events --no-default-features --features apigw + cargo test --package aws_lambda_events --no-default-features --features appsync + cargo test --package aws_lambda_events --no-default-features --features autoscaling + cargo test --package aws_lambda_events --no-default-features --features chime_bot + cargo test --package aws_lambda_events --no-default-features --features clientvpn + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_logs + cargo test --package aws_lambda_events --no-default-features --features code_commit + cargo test --package aws_lambda_events --no-default-features --features codebuild + cargo test --package aws_lambda_events --no-default-features --features codedeploy + cargo test --package aws_lambda_events --no-default-features --features codepipeline_cloudwatch + cargo test --package aws_lambda_events --no-default-features --features codepipeline_job + cargo test --package aws_lambda_events --no-default-features --features cognito + cargo test --package aws_lambda_events --no-default-features --features config + cargo test --package aws_lambda_events --no-default-features --features connect + cargo test --package aws_lambda_events --no-default-features --features dynamodb + cargo test --package aws_lambda_events --no-default-features --features ecr_scan + cargo test --package aws_lambda_events --no-default-features --features firehose + cargo test --package aws_lambda_events --no-default-features --features iam + cargo test --package aws_lambda_events --no-default-features --features iot + cargo test --package aws_lambda_events --no-default-features --features iot_1_click + cargo test --package aws_lambda_events --no-default-features --features iot_button + cargo test --package aws_lambda_events --no-default-features --features iot_deprecated + cargo test --package aws_lambda_events --no-default-features --features kafka + cargo test --package aws_lambda_events --no-default-features --features kinesis + cargo test --package aws_lambda_events --no-default-features --features kinesis_analytics + cargo test --package aws_lambda_events --no-default-features --features lambda_function_urls + cargo test --package aws_lambda_events --no-default-features --features lex + cargo test --package aws_lambda_events --no-default-features --features rabbitmq + cargo test --package aws_lambda_events --no-default-features --features s3 + cargo test --package aws_lambda_events --no-default-features --features s3_batch_job + cargo test --package aws_lambda_events --no-default-features --features ses + cargo test --package aws_lambda_events --no-default-features --features sns + cargo test --package aws_lambda_events --no-default-features --features sqs + cargo test --package aws_lambda_events --no-default-features --features streams + +fmt: + cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 57be6bde..b1108c63 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.9.0" +version = "0.10.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", @@ -13,29 +13,26 @@ repository = "https://github.com/awslabs/aws-lambda-rust-runtime" readme = "README.md" keywords = ["lambda", "aws", "amazon", "events", "S3"] categories = ["api-bindings", "encoding", "web-programming"] +edition = "2021" [dependencies] base64 = "0.21" -http = "0.2" -http-body = "0.4" -http-serde = "^1" -serde = "^1" -serde_derive = "^1" +http = { version = "0.2", optional = true } +http-body = { version = "0.4", optional = true } +http-serde = { version = "^1", optional = true } +serde = { version = "^1", features = ["derive"] } serde_with = { version = "^3", features = ["json"], optional = true } serde_json = "^1" serde_dynamo = { version = "^4.1", optional = true } -bytes = { version = "1", features = ["serde"] } +bytes = { version = "1", features = ["serde"], optional = true } chrono = { version = "0.4.23", default-features = false, features = [ "clock", "serde", "std", -] } -query_map = { version = "^0.6", features = ["serde", "url-query"] } +], optional = true } +query_map = { version = "^0.6", features = ["serde", "url-query"], optional = true } flate2 = { version = "1.0.24", optional = true } -[dev-dependencies] -pretty_assertions = "1.3" - [features] default = [ "activemq", @@ -78,40 +75,40 @@ default = [ ] activemq = [] -alb = [] -apigw = [] +alb = ["bytes", "http", "http-body", "http-serde", "query_map"] +apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] appsync = [] -autoscaling = [] -chime_bot = [] +autoscaling = ["chrono"] +chime_bot = ["chrono"] clientvpn = [] -cloudwatch_events = [] +cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] -code_commit = [] -codebuild = [] -codedeploy = [] +code_commit = ["chrono"] +codebuild = ["chrono"] +codedeploy = ["chrono"] codepipeline = [] -codepipeline_cloudwatch = [] +codepipeline_cloudwatch = ["chrono"] codepipeline_job = [] cognito = [] config = [] connect = [] -dynamodb = ["streams", "serde_dynamo"] +dynamodb = ["chrono", "serde_dynamo", "streams"] ecr_scan = [] -firehose = [] +firehose = ["chrono"] iam = [] -iot = ["iam"] +iot = ["bytes", "http", "http-body", "http-serde", "iam"] iot_1_click = [] iot_button = [] iot_deprecated = ["iot"] -kafka = [] -kinesis = [] +kafka = ["chrono"] +kinesis = ["chrono"] kinesis_analytics = ["kinesis"] -lambda_function_urls = [] +lambda_function_urls = ["bytes", "http", "http-body", "http-serde"] lex = [] rabbitmq = [] -s3 = [] +s3 = ["bytes", "chrono", "http", "http-body", "http-serde"] s3_batch_job = ["s3"] -ses = [] -sns = ["serde_with"] +ses = ["chrono"] +sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index c57ccd6a..94d0e2f5 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,6 +1,9 @@ use chrono::{DateTime, TimeZone, Utc}; -use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; use serde::ser::Serializer; +use serde::{ + de::{Deserializer, Error as DeError, Visitor}, + Deserialize, +}; use std::fmt; // Jan 2, 2006 3:04:05 PM @@ -10,7 +13,7 @@ struct TimeVisitor; impl<'de> Visitor<'de> for TimeVisitor { type Value = DateTime; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) } @@ -74,7 +77,7 @@ mod tests { #[serde(with = "str_time")] pub date: TestTime, } - let data = json!({ + let data = serde_json::json!({ "date": "Sep 1, 2017 4:12:29 PM" }); @@ -92,7 +95,7 @@ mod tests { #[serde(with = "optional_time")] pub date: Option, } - let data = json!({ + let data = serde_json::json!({ "date": "Sep 1, 2017 4:12:29 PM" }); diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 54fc64e4..82fd51df 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -14,13 +14,13 @@ fn ne_timestamp(ts: T) -> SerdeError { } impl fmt::Debug for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ChronoSerdeError({})", self) } } impl fmt::Display for SerdeError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SerdeError::NonExistent { ref timestamp } => { write!(f, "value is not a legal timestamp: {}", timestamp) @@ -77,7 +77,7 @@ where impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { type Value = DateTime; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a unix timestamp as a float") } diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 904ccd9b..9cb89e40 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -49,7 +49,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { type Value = HeaderMap; // Format a message stating what data this Visitor expects to receive. - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("lots of things can go wrong with HeaderMap") } @@ -81,7 +81,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { let mut map = HeaderMap::with_capacity(access.size_hint().unwrap_or(0)); if !self.is_human_readable { - while let Some((key, arr)) = access.next_entry::, Vec>>()? { + while let Some((key, arr)) = access.next_entry::, Vec>>()? { let key = HeaderName::from_bytes(key.as_bytes()) .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; for val in arr { @@ -91,7 +91,7 @@ impl<'de> Visitor<'de> for HeaderMapVisitor { } } } else { - while let Some((key, val)) = access.next_entry::, OneOrMore>()? { + while let Some((key, val)) = access.next_entry::, OneOrMore<'_>>()? { let key = HeaderName::from_bytes(key.as_bytes()) .map_err(|_| de::Error::invalid_value(Unexpected::Str(&key), &self))?; match val { @@ -135,6 +135,7 @@ where #[cfg(test)] mod tests { use super::*; + use serde::{Deserialize, Serialize}; #[test] fn test_deserialize_missing_http_headers() { @@ -143,7 +144,7 @@ mod tests { #[serde(deserialize_with = "deserialize_headers", default)] pub headers: HeaderMap, } - let data = json!({ + let data = serde_json::json!({ "not_headers": {} }); @@ -161,7 +162,7 @@ mod tests { #[serde(serialize_with = "serialize_multi_value_headers")] headers: HeaderMap, } - let data = json!({ + let data = serde_json::json!({ "headers": { "Accept": ["*/*"] } @@ -181,7 +182,7 @@ mod tests { #[serde(deserialize_with = "deserialize_headers")] headers: HeaderMap, } - let data = json!({ "headers": null }); + let data = serde_json::json!({ "headers": null }); let decoded: Test = serde_json::from_value(data).unwrap(); assert!(decoded.headers.is_empty()); diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 6060a429..63a98eb4 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -11,7 +11,7 @@ struct MethodVisitor; impl<'de> Visitor<'de> for MethodVisitor { type Value = Method; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { write!(formatter, "valid method name") } @@ -56,6 +56,7 @@ pub fn serialize_optional(method: &Option, ser: S) -> Res #[cfg(test)] mod tests { use super::*; + use serde::{Deserialize, Serialize}; #[test] fn test_http_method_serializer() { @@ -64,7 +65,7 @@ mod tests { #[serde(with = "crate::custom_serde::http_method")] pub method: http::Method, } - let data = json!({ + let data = serde_json::json!({ "method": "DELETE" }); let decoded: Test = serde_json::from_value(data.clone()).unwrap(); @@ -83,7 +84,7 @@ mod tests { #[serde(default)] pub method: Option, } - let data = json!({ + let data = serde_json::json!({ "method": "DELETE" }); let decoded: Test = serde_json::from_value(data.clone()).unwrap(); @@ -92,11 +93,11 @@ mod tests { let recoded = serde_json::to_value(decoded).unwrap(); assert_eq!(data, recoded); - let data = json!({ "method": null }); + let data = serde_json::json!({ "method": null }); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(None, decoded.method); - let data = json!({}); + let data = serde_json::json!({}); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(None, decoded.method); } diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index e2c44af9..65f0f89a 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -1,7 +1,4 @@ -#[allow(unused)] use base64::Engine; -use chrono::{DateTime, Duration, TimeZone, Utc}; -use serde; use serde::de::{Deserialize, Deserializer, Error as DeError}; use serde::ser::Serializer; use std::collections::HashMap; @@ -34,89 +31,6 @@ pub(crate) mod float_unix_epoch; #[cfg(any(feature = "alb", feature = "apigw"))] pub(crate) mod http_method; -fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> -where - D: Deserializer<'de>, -{ - #[derive(Deserialize)] - #[serde(untagged)] - enum StringOrNumber { - String(String), - Float(f64), - Int(u64), - } - - let input: f64 = match StringOrNumber::deserialize(deserializer)? { - StringOrNumber::String(s) => s.parse::().map_err(DeError::custom)?, - StringOrNumber::Float(f) => f, - StringOrNumber::Int(i) => i as f64, - }; - - // We need to do this due to floating point issues. - let input_as_string = format!("{}", input); - let parts: Result, _> = input_as_string - .split('.') - .map(|x| x.parse::().map_err(DeError::custom)) - .collect(); - let parts = parts?; - if parts.len() > 1 { - Ok((parts[0], parts[1])) - } else { - Ok((parts[0], 0)) - } -} - -pub(crate) fn serialize_milliseconds(date: &DateTime, serializer: S) -> Result -where - S: Serializer, -{ - let ts_with_millis = date.timestamp_millis(); - serializer.serialize_str(&ts_with_millis.to_string()) -} - -pub(crate) fn deserialize_milliseconds<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let (whole, frac) = normalize_timestamp(deserializer)?; - assert_eq!(frac, 0); - let seconds: f64 = whole as f64 / 1000.0; - let milliseconds: u32 = (seconds.fract() * 1000f64) as u32; - let nanos = milliseconds * 1_000_000; - Utc.timestamp_opt(seconds as i64, nanos) - .latest() - .ok_or_else(|| D::Error::custom("invalid timestamp")) -} - -pub(crate) fn serialize_seconds(date: &DateTime, serializer: S) -> Result -where - S: Serializer, -{ - let seconds = date.timestamp(); - let milliseconds = date.timestamp_subsec_millis(); - let whole_seconds = seconds + (milliseconds as i64 / 1000); - let subsec_millis = milliseconds % 1000; - if milliseconds > 0 { - let combined = format!("{}.{:03}", whole_seconds, subsec_millis); - serializer.serialize_str(&combined) - } else { - serializer.serialize_str(&whole_seconds.to_string()) - } -} - -#[allow(dead_code)] -pub(crate) fn deserialize_seconds<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let (whole, frac) = normalize_timestamp(deserializer)?; - let seconds = whole; - let nanos = frac * 1_000_000; - Utc.timestamp_opt(seconds as i64, nanos as u32) - .latest() - .ok_or_else(|| D::Error::custom("invalid timestamp")) -} - pub(crate) fn deserialize_base64<'de, D>(deserializer: D) -> Result, D::Error> where D: Deserializer<'de>, @@ -159,46 +73,13 @@ where Ok(opt.unwrap_or_default()) } -pub(crate) fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - let seconds = duration.num_seconds(); - - serializer.serialize_i64(seconds) -} - -pub(crate) fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let seconds = f64::deserialize(deserializer)?; - Ok(Duration::seconds(seconds as i64)) -} - -pub(crate) fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result -where - S: Serializer, -{ - let minutes = duration.num_minutes(); - - serializer.serialize_i64(minutes) -} - -pub(crate) fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result -where - D: Deserializer<'de>, -{ - let minutes = f64::deserialize(deserializer)?; - Ok(Duration::minutes(minutes as i64)) -} - /// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. #[cfg(any( feature = "alb", feature = "apigw", feature = "cloudwatch_events", feature = "code_commit", + feature = "cognito", test ))] pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result @@ -214,7 +95,7 @@ where #[allow(deprecated)] mod test { use super::*; - use chrono::TimeZone; + use serde::{Deserialize, Serialize}; use serde_json; #[test] @@ -224,7 +105,7 @@ mod test { #[serde(deserialize_with = "deserialize_base64")] v: Vec, } - let data = json!({ + let data = serde_json::json!({ "v": "SGVsbG8gV29ybGQ=", }); let decoded: Test = serde_json::from_value(data).unwrap(); @@ -245,76 +126,6 @@ mod test { assert_eq!(encoded, r#"{"v":"SGVsbG8gV29ybGQ="}"#.to_string()); } - #[test] - fn test_deserialize_milliseconds() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_milliseconds")] - v: DateTime, - } - let expected = Utc.ymd(2017, 10, 5).and_hms_nano(15, 33, 44, 302_000_000); - - // Test parsing strings. - let data = json!({ - "v": "1507217624302", - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - // Test parsing ints. - let decoded: Test = serde_json::from_slice(r#"{"v":1507217624302}"#.as_bytes()).unwrap(); - assert_eq!(expected, decoded.v,); - // Test parsing floats. - let data = json!({ - "v": 1507217624302.0, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_milliseconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_milliseconds")] - v: DateTime, - } - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99_888_777), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600099"}"#)); - } - - #[test] - fn test_serialize_seconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_seconds")] - v: DateTime, - } - - // Make sure nanoseconds are chopped off. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600"}"#)); - - // Make sure milliseconds are included. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 2_000_000), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); - - // Make sure milliseconds are included. - let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); - } - #[test] fn test_deserialize_map() { #[derive(Deserialize)] @@ -322,13 +133,13 @@ mod test { #[serde(deserialize_with = "deserialize_lambda_map")] v: HashMap, } - let input = json!({ + let input = serde_json::json!({ "v": {}, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(HashMap::new(), decoded.v); - let input = json!({ + let input = serde_json::json!({ "v": null, }); let decoded: Test = serde_json::from_value(input).unwrap(); @@ -343,93 +154,19 @@ mod test { #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] v: serde_dynamo::Item, } - let input = json!({ + let input = serde_json::json!({ "v": {}, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); - let input = json!({ + let input = serde_json::json!({ "v": null, }); let decoded: Test = serde_json::from_value(input).unwrap(); assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); } - #[test] - fn test_deserialize_duration_seconds() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_duration_seconds")] - v: Duration, - } - - let expected = Duration::seconds(36); - - let data = json!({ - "v": 36, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - - let data = json!({ - "v": 36.1, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_duration_seconds() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_duration_seconds")] - v: Duration, - } - let instance = Test { - v: Duration::seconds(36), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":36}"#)); - } - - #[test] - fn test_deserialize_duration_minutes() { - #[derive(Deserialize)] - struct Test { - #[serde(deserialize_with = "deserialize_duration_minutes")] - v: Duration, - } - - let expected = Duration::minutes(36); - - let data = json!({ - "v": 36, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - - let data = json!({ - "v": 36.1, - }); - let decoded: Test = serde_json::from_value(data).unwrap(); - assert_eq!(expected, decoded.v,); - } - - #[test] - fn test_serialize_duration_minutes() { - #[derive(Serialize)] - struct Test { - #[serde(serialize_with = "serialize_duration_minutes")] - v: Duration, - } - let instance = Test { - v: Duration::minutes(36), - }; - let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":36}"#)); - } - #[test] fn test_deserialize_nullish_boolean() { #[derive(Deserialize)] diff --git a/lambda-events/src/encodings.rs b/lambda-events/src/encodings/http.rs similarity index 75% rename from lambda-events/src/encodings.rs rename to lambda-events/src/encodings/http.rs index 42dd15a7..effb48f4 100644 --- a/lambda-events/src/encodings.rs +++ b/lambda-events/src/encodings/http.rs @@ -1,124 +1,9 @@ -use super::custom_serde::*; -use chrono::{DateTime, Duration, Utc}; -use std::{borrow::Cow, mem::take, ops::Deref, ops::DerefMut, pin::Pin, task::Poll}; - use base64::display::Base64Display; use bytes::Bytes; use http_body::{Body as HttpBody, SizeHint}; use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; use serde::ser::{Error as SerError, Serialize, Serializer}; - -pub type Error = Box; - -/// Binary data encoded in base64. -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct Base64Data( - #[serde(deserialize_with = "deserialize_base64")] - #[serde(serialize_with = "serialize_base64")] - pub Vec, -); - -impl Deref for Base64Data { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for Base64Data { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Timestamp with millisecond precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct MillisecondTimestamp( - #[serde(deserialize_with = "deserialize_milliseconds")] - #[serde(serialize_with = "serialize_milliseconds")] - pub DateTime, -); - -impl Deref for MillisecondTimestamp { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MillisecondTimestamp { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Timestamp with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct SecondTimestamp( - #[serde(deserialize_with = "deserialize_seconds")] - #[serde(serialize_with = "serialize_seconds")] - pub DateTime, -); - -impl Deref for SecondTimestamp { - type Target = DateTime; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SecondTimestamp { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Duration with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct SecondDuration( - #[serde(deserialize_with = "deserialize_duration_seconds")] - #[serde(serialize_with = "serialize_duration_seconds")] - pub Duration, -); - -impl Deref for SecondDuration { - type Target = Duration; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for SecondDuration { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Duration with minute precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] -pub struct MinuteDuration( - #[serde(deserialize_with = "deserialize_duration_minutes")] - #[serde(serialize_with = "serialize_duration_minutes")] - pub Duration, -); - -impl Deref for MinuteDuration { - type Target = Duration; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for MinuteDuration { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} +use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// Representation of http request and response bodies as supported /// by API Gateway and ALBs. @@ -313,7 +198,7 @@ impl<'de> Deserialize<'de> for Body { impl<'de> Visitor<'de> for BodyVisitor { type Value = Body; - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { formatter.write_str("string") } @@ -331,7 +216,7 @@ impl<'de> Deserialize<'de> for Body { impl HttpBody for Body { type Data = Bytes; - type Error = Error; + type Error = super::Error; fn poll_data( self: Pin<&mut Self>, diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs new file mode 100644 index 00000000..ccc92684 --- /dev/null +++ b/lambda-events/src/encodings/mod.rs @@ -0,0 +1,37 @@ +use serde::{Deserialize, Serialize}; +use std::{ops::Deref, ops::DerefMut}; + +#[cfg(feature = "chrono")] +mod time; +use crate::custom_serde::{deserialize_base64, serialize_base64}; + +#[cfg(feature = "chrono")] +pub use self::time::*; +#[cfg(feature = "http")] +mod http; +#[cfg(feature = "http")] +pub use self::http::*; + +pub type Error = Box; + +/// Binary data encoded in base64. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Base64Data( + #[serde(deserialize_with = "deserialize_base64")] + #[serde(serialize_with = "serialize_base64")] + pub Vec, +); + +impl Deref for Base64Data { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for Base64Data { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs new file mode 100644 index 00000000..390927ca --- /dev/null +++ b/lambda-events/src/encodings/time.rs @@ -0,0 +1,363 @@ +use chrono::{DateTime, Duration, TimeZone, Utc}; +use serde::ser::Serializer; +use serde::{ + de::{Deserializer, Error as DeError}, + Deserialize, Serialize, +}; +use std::ops::{Deref, DerefMut}; + +/// Timestamp with millisecond precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MillisecondTimestamp( + #[serde(deserialize_with = "deserialize_milliseconds")] + #[serde(serialize_with = "serialize_milliseconds")] + pub DateTime, +); + +impl Deref for MillisecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MillisecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Timestamp with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondTimestamp( + #[serde(deserialize_with = "deserialize_seconds")] + #[serde(serialize_with = "serialize_seconds")] + pub DateTime, +); + +impl Deref for SecondTimestamp { + type Target = DateTime; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondTimestamp { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with second precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct SecondDuration( + #[serde(deserialize_with = "deserialize_duration_seconds")] + #[serde(serialize_with = "serialize_duration_seconds")] + pub Duration, +); + +impl Deref for SecondDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for SecondDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Duration with minute precision. +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct MinuteDuration( + #[serde(deserialize_with = "deserialize_duration_minutes")] + #[serde(serialize_with = "serialize_duration_minutes")] + pub Duration, +); + +impl Deref for MinuteDuration { + type Target = Duration; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for MinuteDuration { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +fn serialize_milliseconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let ts_with_millis = date.timestamp_millis(); + serializer.serialize_str(&ts_with_millis.to_string()) +} + +fn deserialize_milliseconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + assert_eq!(frac, 0); + let seconds: f64 = whole as f64 / 1000.0; + let milliseconds: u32 = (seconds.fract() * 1000f64) as u32; + let nanos = milliseconds * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +fn serialize_seconds(date: &DateTime, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = date.timestamp(); + let milliseconds = date.timestamp_subsec_millis(); + let whole_seconds = seconds + (milliseconds as i64 / 1000); + let subsec_millis = milliseconds % 1000; + if milliseconds > 0 { + let combined = format!("{}.{:03}", whole_seconds, subsec_millis); + serializer.serialize_str(&combined) + } else { + serializer.serialize_str(&whole_seconds.to_string()) + } +} + +fn deserialize_seconds<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let (whole, frac) = normalize_timestamp(deserializer)?; + let seconds = whole; + let nanos = frac * 1_000_000; + Utc.timestamp_opt(seconds as i64, nanos as u32) + .latest() + .ok_or_else(|| D::Error::custom("invalid timestamp")) +} + +fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let seconds = duration.num_seconds(); + + serializer.serialize_i64(seconds) +} + +fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let seconds = f64::deserialize(deserializer)?; + Ok(Duration::seconds(seconds as i64)) +} + +fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result +where + S: Serializer, +{ + let minutes = duration.num_minutes(); + + serializer.serialize_i64(minutes) +} + +fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let minutes = f64::deserialize(deserializer)?; + Ok(Duration::minutes(minutes as i64)) +} + +fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> +where + D: Deserializer<'de>, +{ + #[derive(Deserialize)] + #[serde(untagged)] + enum StringOrNumber { + String(String), + Float(f64), + Int(u64), + } + + let input: f64 = match StringOrNumber::deserialize(deserializer)? { + StringOrNumber::String(s) => s.parse::().map_err(DeError::custom)?, + StringOrNumber::Float(f) => f, + StringOrNumber::Int(i) => i as f64, + }; + + // We need to do this due to floating point issues. + let input_as_string = format!("{}", input); + let parts: Result, _> = input_as_string + .split('.') + .map(|x| x.parse::().map_err(DeError::custom)) + .collect(); + let parts = parts?; + if parts.len() > 1 { + Ok((parts[0], parts[1])) + } else { + Ok((parts[0], 0)) + } +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + use serde_json; + + #[test] + fn test_deserialize_milliseconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_milliseconds")] + v: DateTime, + } + let expected = Utc.ymd(2017, 10, 5).and_hms_nano(15, 33, 44, 302_000_000); + + // Test parsing strings. + let data = serde_json::json!({ + "v": "1507217624302", + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing ints. + let decoded: Test = serde_json::from_slice(r#"{"v":1507217624302}"#.as_bytes()).unwrap(); + assert_eq!(expected, decoded.v,); + // Test parsing floats. + let data = serde_json::json!({ + "v": 1507217624302.0, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_milliseconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_milliseconds")] + v: DateTime, + } + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99_888_777), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600099"}"#)); + } + + #[test] + fn test_serialize_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_seconds")] + v: DateTime, + } + + // Make sure nanoseconds are chopped off. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 99), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 2_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); + + // Make sure milliseconds are included. + let instance = Test { + v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); + } + + #[test] + fn test_deserialize_duration_seconds() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_seconds")] + v: Duration, + } + + let expected = Duration::seconds(36); + + let data = serde_json::json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = serde_json::json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_seconds() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_seconds")] + v: Duration, + } + let instance = Test { + v: Duration::seconds(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } + + #[test] + fn test_deserialize_duration_minutes() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_duration_minutes")] + v: Duration, + } + + let expected = Duration::minutes(36); + + let data = serde_json::json!({ + "v": 36, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + + let data = serde_json::json!({ + "v": 36.1, + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.v,); + } + + #[test] + fn test_serialize_duration_minutes() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_duration_minutes")] + v: Duration, + } + let instance = Test { + v: Duration::minutes(36), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + assert_eq!(encoded, String::from(r#"{"v":36}"#)); + } +} diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index fcb490ec..9469ece4 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { @@ -52,7 +54,7 @@ pub struct ActiveMqDestination { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "activemq")] diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index b9a69ce5..259dce23 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,7 +1,10 @@ -use crate::custom_serde::{http_method, serialize_headers, serialize_multi_value_headers}; +use crate::custom_serde::{ + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, +}; use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; +use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -15,13 +18,14 @@ pub struct AlbTargetGroupRequest { pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap, - #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] pub headers: HeaderMap, - #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_multi_value_headers")] pub multi_value_headers: HeaderMap, pub request_context: AlbTargetGroupRequestContext, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub body: Option, } @@ -57,6 +61,7 @@ pub struct AlbTargetGroupResponse { pub multi_value_headers: HeaderMap, #[serde(skip_serializing_if = "Option::is_none")] pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, } @@ -64,7 +69,7 @@ pub struct AlbTargetGroupResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "alb")] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 196bff86..917f06aa 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -6,7 +6,7 @@ use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; @@ -751,7 +751,7 @@ pub struct IamPolicyStatement { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "apigw")] diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 0ef67b7b..120fc9e3 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use map[string]string, json.RawMessage, interface{}, etc.. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -121,7 +122,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "appsync")] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index ce0128c2..cc003daf 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -1,10 +1,11 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -47,7 +48,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "autoscaling")] diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index ef57c6f9..6581ed2c 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index f0e61dda..0e188704 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { @@ -47,7 +49,7 @@ pub struct ClientVpnConnectionHandlerResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "clientvpn")] diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index aefa7f4a..36d071ea 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index 0dd2b540..1bd44297 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index 86a1de15..ce5fa47c 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c4e26b4e..c8eb7834 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 942e5984..87fb8085 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index 1369a793..fb5c50a7 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use crate::custom_serde::deserialize_nullish_boolean; diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index f752f53e..08f05929 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index 3c8acbf9..2a6a82e3 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Event { diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index ac6f8926..74a76e70 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index 4ce78b71..37d18d7a 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 3c39e3d3..425de865 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -1,6 +1,6 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; pub mod cloudtrail; diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index c75f1b5e..d1c192e5 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 4d256e3b..1cd73e6e 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 33092b76..7d161822 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -1,5 +1,4 @@ -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index a826ed07..fa6ffc3b 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationStepStatusChange { diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index 573a99ea..d5bc9681 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -1,7 +1,6 @@ use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index ce6cf79f..6a7e25d3 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -1,8 +1,6 @@ +use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use serde_derive::Deserialize; -use serde_derive::Serialize; - #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CheckItemRefreshNotification { diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 053974ec..0c9ad4a8 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -59,7 +59,7 @@ impl<'de> Deserialize<'de> for AwsLogs { impl<'de> Visitor<'de> for AwsLogsVisitor { type Value = AwsLogs; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { formatter.write_str("a base64 gzipped string") } diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 8b2e617f..87687cfd 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use crate::custom_serde::deserialize_nullish_boolean; @@ -68,7 +69,7 @@ pub struct CodeCommitReference { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "code_commit")] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 2c6ddf39..a3839f92 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -1,8 +1,8 @@ -use crate::custom_serde::*; +use crate::custom_serde::{codebuild_time, CodeBuildNumber}; use crate::encodings::{MinuteDuration, SecondDuration}; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; pub type CodeBuildPhaseStatus = String; @@ -213,7 +213,7 @@ pub type CodeBuildTime = DateTime; mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codebuild")] diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 61bfc665..2ab37a82 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; pub type CodeDeployDeploymentState = String; @@ -68,7 +69,7 @@ pub struct CodeDeployEventDetail { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codedeploy")] diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index d4d3477b..f26aa54f 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; pub type CodePipelineStageState = String; @@ -80,7 +81,7 @@ pub struct CodePipelineEventDetailType { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codepipeline_cloudwatch")] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 0767b272..6c5d75f6 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -115,7 +117,7 @@ pub struct CodePipelineArtifactCredentials { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "codepipeline_job")] diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 99bc682b..6874ee24 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; + /// `CognitoEvent` contains data from an event sent from AWS Cognito Sync #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -457,7 +458,7 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "cognito")] diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index bb5d0c11..0b03ecc5 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `ConfigEvent` contains data from an event sent from AWS Config #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -38,7 +40,7 @@ pub struct ConfigEvent { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "config")] diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 62e86b52..bc640930 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `ConnectEvent` contains the data structure for a Connect event. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -92,7 +94,7 @@ pub type ConnectResponse = HashMap; mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "connect")] diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index d2f32caf..aad2cd4b 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -1,5 +1,5 @@ use base64::Engine; -use event::serde_dynamo::AttributeValue; +use serde_dynamo::AttributeValue; use std::collections::HashMap; #[cfg(test)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 00ff08e4..398f2dd5 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -1,6 +1,6 @@ -use crate::custom_serde::*; -use crate::streams::DynamoDbBatchItemFailure; +use crate::custom_serde::deserialize_lambda_dynamodb_item; use crate::time_window::*; +use crate::{custom_serde::float_unix_epoch, streams::DynamoDbBatchItemFailure}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -252,7 +252,7 @@ mod test { use super::*; use chrono::TimeZone; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "dynamodb")] diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 87dede6f..1ed91896 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { @@ -59,7 +61,7 @@ pub struct EcrScanEventFindingSeverityCounts { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "ecr_scan")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 63ef3e1f..352342ec 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -1,5 +1,8 @@ -use crate::custom_serde::*; -use crate::encodings::{Base64Data, MillisecondTimestamp}; +use crate::{ + custom_serde::deserialize_lambda_map, + encodings::{Base64Data, MillisecondTimestamp}, +}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. @@ -73,7 +76,7 @@ pub struct KinesisFirehoseRecordMetadata { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "firehose")] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 1b73e44b..12bf7ba9 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `IamPolicyDocument` represents an IAM policy document. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 9f45899f..31220b17 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,7 +1,8 @@ -use crate::custom_serde::*; +use crate::custom_serde::serialize_headers; use crate::encodings::Base64Data; use crate::iam::IamPolicyDocument; use http::HeaderMap; +use serde::{Deserialize, Serialize}; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html @@ -75,7 +76,7 @@ pub struct IoTCoreCustomAuthorizerResponse { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot")] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 0e1c11b6..4ec47d71 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -57,8 +59,7 @@ pub struct IoTOneClickPlacementInfo { #[cfg(test)] mod test { use super::*; - - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot_1_click")] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 32ba8d5a..ac79e34b 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { @@ -13,7 +15,7 @@ pub struct IoTButtonEvent { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "iot_button")] diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 4304d7cd..12c1df99 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -1,4 +1,5 @@ use crate::iot::*; +use serde::{Deserialize, Serialize}; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 6c4d78fa..07299859 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -1,5 +1,5 @@ -use crate::custom_serde::*; -use crate::encodings::MillisecondTimestamp; +use crate::{custom_serde::deserialize_lambda_map, encodings::MillisecondTimestamp}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -35,7 +35,7 @@ pub struct KafkaRecord { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "kafka")] diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index 1704009e..74c95606 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -1,4 +1,5 @@ use crate::encodings::Base64Data; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index c401fa72..0c43ae10 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -1,5 +1,6 @@ use crate::encodings::{Base64Data, SecondTimestamp}; use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; +use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -74,7 +75,7 @@ pub struct KinesisRecord { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "kinesis")] diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index d1567b56..37ddfe39 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::*; use http::HeaderMap; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; + /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index a3593dfd..a058a249 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::*; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { @@ -106,7 +108,7 @@ pub struct Attachment { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "lex")] diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index e7b8c7f7..1aa56697 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -67,8 +67,6 @@ pub mod connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] -extern crate serde_dynamo; -#[cfg(feature = "dynamodb")] pub mod dynamodb; /// AWS Lambda event definitions for ecr_scan. diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index c8af802f..14a379a5 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { @@ -62,7 +63,7 @@ where mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "rabbitmq")] diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index 8db71896..e3eb691e 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index b25cfdd2..13d514ad 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// `S3Event` which wrap an array of `S3Event`Record #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -90,7 +92,7 @@ pub struct S3Object { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "s3")] diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index e31a751e..1eec2c0d 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -1,10 +1,11 @@ -use crate::custom_serde::*; use http::HeaderMap; use serde::de::DeserializeOwned; -use serde::ser::Serialize; +use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +use crate::custom_serde::{deserialize_headers, serialize_headers}; + /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas /// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -117,7 +118,7 @@ pub struct SessionIssuer { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "s3")] diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 3570652f..a4a97039 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -121,7 +122,7 @@ pub struct SimpleEmailDisposition { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "ses")] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 78193fcf..3c859d3e 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,9 +1,10 @@ -use crate::custom_serde::*; use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; +use crate::custom_serde::deserialize_lambda_map; + /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) @@ -178,7 +179,7 @@ pub struct MessageAttribute { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "sns")] diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 5dc178b2..af4d3f21 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,4 +1,5 @@ -use crate::{custom_serde::*, encodings::Base64Data}; +use crate::custom_serde::deserialize_lambda_map; +use crate::encodings::Base64Data; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -115,7 +116,7 @@ pub struct BatchItemFailure { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] #[cfg(feature = "sqs")] diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 51a77121..9e0fd76f 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -1,3 +1,5 @@ +use serde::{Deserialize, Serialize}; + /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index fa6cce05..564debd7 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,27 +1,13 @@ -extern crate base64; -extern crate http_serde; -#[cfg(test)] -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate serde_derive; -#[cfg(test)] -#[macro_use] -extern crate serde_json; - -// Crates with types that we use publicly. Reexported for ease of interoperability. -pub extern crate bytes; -pub extern crate chrono; -pub extern crate http; -pub extern crate http_body; -pub extern crate query_map; -pub extern crate serde; -#[cfg(not(test))] -pub extern crate serde_json; +#![deny(rust_2018_idioms)] +#[cfg(feature = "http")] +pub use http; +#[cfg(feature = "query_map")] +pub use query_map; mod custom_serde; /// Encodings used in AWS Lambda json event values. pub mod encodings; +#[cfg(feature = "chrono")] pub mod time_window; /// AWS Lambda event definitions. diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 9418dc8c..9f035995 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -65,7 +66,7 @@ pub struct TimeWindowEventResponseProperties { mod test { use super::*; - extern crate serde_json; + use serde_json; #[test] fn test_window_deserializer() { diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 289aec17..1409555e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -40,7 +40,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.9.0" +version = "0.10.0" default-features = false features = ["alb", "apigw"] From 19b83261017dfb7b05bb90ddea0b617fee303c3b Mon Sep 17 00:00:00 2001 From: mack <24418071+heavens@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:58:12 -0700 Subject: [PATCH 006/211] change(example/basic-lambda-external-runtime): minimal example showcasing lambda_runtime usage alongside another runtime thread/thread pool. (#661) --- .../basic-lambda-external-runtime/Cargo.toml | 15 +++ .../basic-lambda-external-runtime/README.md | 11 ++ .../basic-lambda-external-runtime/src/main.rs | 104 ++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 examples/basic-lambda-external-runtime/Cargo.toml create mode 100644 examples/basic-lambda-external-runtime/README.md create mode 100644 examples/basic-lambda-external-runtime/src/main.rs diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml new file mode 100644 index 00000000..9c732b2f --- /dev/null +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "basic-lambda-external-runtime" +version = "0.1.0" +edition = "2021" + +[dependencies] +async-channel = "1.8.0" +futures-lite = "1.13.0" +lambda_runtime = "0.8.0" +lambda_runtime_api_client = "0.8.0" +serde = "1.0.163" +tokio = "1.28.2" +tokio-test = "0.4.2" +tracing = "0.1.37" +tracing-subscriber = "0.3.17" diff --git a/examples/basic-lambda-external-runtime/README.md b/examples/basic-lambda-external-runtime/README.md new file mode 100644 index 00000000..498f8a50 --- /dev/null +++ b/examples/basic-lambda-external-runtime/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs new file mode 100644 index 00000000..71bd123b --- /dev/null +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -0,0 +1,104 @@ +use std::{io, thread}; + +use futures_lite::future; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; +use tokio::runtime::Builder; + +/// This is also a made-up example. Requests come into the runtime as unicode +/// strings in json format, which can map to any structure that implements `serde::Deserialize` +/// The runtime pays no attention to the contents of the request payload. +#[derive(Deserialize)] +struct Request { + command: String, +} + +/// This is a made-up example of what a response structure may look like. +/// There is no restriction on what it can be. The runtime requires responses +/// to be serialized into json. The runtime pays no attention +/// to the contents of the response payload. +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +fn main() -> Result<(), io::Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + // Create a channel used to send and receive outputs from our lambda handler. Realistically, this would be either an unbounded channel + // or a bounded channel with a higher capacity as needed. + let (lambda_tx, lambda_rx) = async_channel::bounded(1); + + // Create a bounded channel used to communicate our shutdown signal across threads. + let (shutdown_tx, shutdown_rx) = async_channel::bounded(1); + + // Build a single-threaded (or multi-threaded using Builder::new_multi_thread) runtime to spawn our lambda work onto. + let tokio_runtime = Builder::new_current_thread() + .thread_name("lambda-runtime") + .enable_all() + .build() + .expect("build lambda runtime"); + + // Run the lambda runtime worker thread to completion. The response is sent to the other "runtime" to be processed as needed. + thread::spawn(move || { + let func = service_fn(my_handler); + if let Ok(response) = tokio_runtime.block_on(lambda_runtime::run(func)) { + lambda_tx.send_blocking(response).expect("send lambda result"); + }; + }); + + // Run the mock runtime to completion. + my_runtime(move || future::block_on(app_runtime_task(lambda_rx.clone(), shutdown_tx.clone()))); + + // Block the main thread until a shutdown signal is received. + future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) +} + +pub(crate) async fn my_handler(event: LambdaEvent) -> Result { + // extract some useful info from the request + let command = event.payload.command; + + // prepare the response + let resp = Response { + req_id: event.context.request_id, + msg: format!("Command {} executed.", command), + }; + + // return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(resp) +} + +/// A task to be ran on the custom runtime. Once a response from the lambda runtime is received then a shutdown signal +/// is sent to the main thread notifying the process to exit. +pub(crate) async fn app_runtime_task(lambda_rx: async_channel::Receiver<()>, shutdown_tx: async_channel::Sender<()>) { + loop { + // Receive the response sent by the lambda handle and process as needed. + if let Ok(result) = lambda_rx.recv().await { + tracing::debug!(?result); + // We're ready to shutdown our app. Send the shutdown signal notifying the main thread to exit the process. + shutdown_tx.send(()).await.expect("send shutdown signal"); + break; + } + + // more app logic would be here... + } +} + +/// Construct the mock runtime worker thread(s) to spawn some work onto. +fn my_runtime(func: impl Fn() + Send + 'static) { + thread::Builder::new() + .name("my-runtime".into()) + .spawn(func) + .expect("spawn my_runtime worker"); +} From fdc471459dc4f8b89b45d32dcfc74472a887242a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 10 Jun 2023 12:41:49 -0700 Subject: [PATCH 007/211] Release version 0.8.1 (#662) There are some patch improvements for http and generic lambda functions. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1409555e..edc68650 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.0" +version = "0.8.1" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 96506022..1de6f361 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.0" +version = "0.8.1" authors = [ "David Calavera ", "Harold Sun ", From b2451e9a47c1f58657a9d08a006c443e6cadf8e1 Mon Sep 17 00:00:00 2001 From: mark-keaton Date: Thu, 15 Jun 2023 18:13:38 -0500 Subject: [PATCH 008/211] fix: Makes two properties optional on MigrateUserResponse. (#663) --- lambda-events/src/event/cognito/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 6874ee24..49f2eebd 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -254,7 +254,9 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { pub final_user_status: Option, #[serde(default)] pub message_action: Option, - pub desired_delivery_mediums: Vec, + #[serde(default)] + pub desired_delivery_mediums: Option>, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub force_alias_creation: bool, } From aa80e74d30ac7d14264291bfee3e302c3b597e4d Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Tue, 20 Jun 2023 04:59:54 +0200 Subject: [PATCH 009/211] Examples basic s3 object lambda thumbnail (#664) * fix example basic-s3-thumbnail test * basic-s3-object-lambda-thumbnail example (#625) Forwards a thumbnail to the user instead of the requested file --- .../Cargo.toml | 34 ++++ .../README.md | 40 ++++ .../src/main.rs | 178 ++++++++++++++++++ .../src/s3.rs | 90 +++++++++ .../testdata/image.png | Bin 0 -> 282 bytes .../testdata/thumbnail.png | Bin 0 -> 82 bytes examples/basic-s3-thumbnail/Cargo.toml | 1 + examples/basic-s3-thumbnail/src/main.rs | 6 +- 8 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 examples/basic-s3-object-lambda-thumbnail/Cargo.toml create mode 100644 examples/basic-s3-object-lambda-thumbnail/README.md create mode 100644 examples/basic-s3-object-lambda-thumbnail/src/main.rs create mode 100644 examples/basic-s3-object-lambda-thumbnail/src/s3.rs create mode 100644 examples/basic-s3-object-lambda-thumbnail/testdata/image.png create mode 100644 examples/basic-s3-object-lambda-thumbnail/testdata/thumbnail.png diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml new file mode 100644 index 00000000..493846ad --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "basic-s3-object-lambda-thumbnail" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws_lambda_events = "0.8.3" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1" } +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +aws-config = "0.55.3" +aws-sdk-s3 = "0.28.0" +thumbnailer = "0.4.0" +mime = "0.3.16" +async-trait = "0.1.66" +ureq = "2.6.2" +aws-smithy-http = "0.55.3" + +[dev-dependencies] +mockall = "0.11.3" +tokio-test = "0.4.2" diff --git a/examples/basic-s3-object-lambda-thumbnail/README.md b/examples/basic-s3-object-lambda-thumbnail/README.md new file mode 100644 index 00000000..e9347fbb --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/README.md @@ -0,0 +1,40 @@ +# AWS S3 Object Lambda Function + +It uses a GetObject event and it returns with a thumbnail instead of the real +object from the S3 bucket. +The thumbnail was tested only witn PNG files. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release --arm64 --output-format zip` +3. Upload the bootstrap.zip file from the directory:`target/lambda/basic-s3-object-lambda-thumbnail/` + +## Setup on AWS S3 + +1. You need a bucket and upload a PNG file to that bucket +2. Set Access Point for that bucket +3. Set Object Lambda Access Point for the access point and use the uploaded lambda function as a transformer + +## Set Up on AWS Lambda + +0. Click on Code tab +1. Runtime settings - runtime: Custom runtime on Amazon Linux 2 +2. Runtime settings - Architecture: arm64 + +## Set Up on AWS IAM + +1. Click on Roles +2. Search the lambda function name +3. Add the permission: AmazonS3ObjectLambdaExecutionRolePolicy + +## How to check this lambda + +1. Go to S3 +2. Click on Object Lambda Access Point +3. Click on your object lambda access point name +4. click on one uploaded PNG file +5. Click on the activated Open button + +### Expected: +A new browser tab opens with a 128x128 thumbnail diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs new file mode 100644 index 00000000..7786f56e --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -0,0 +1,178 @@ +use std::{error, io::Cursor}; + +use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; +use aws_sdk_s3::Client as S3Client; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use s3::{GetFile, SendFile}; +use thumbnailer::{create_thumbnails, ThumbnailSize}; + +mod s3; + +/** +This s3 object lambda handler + * downloads the asked file + * creates a PNG thumbnail from it + * forwards it to the browser +*/ +pub(crate) async fn function_handler( + event: LambdaEvent, + size: u32, + client: &T, +) -> Result> { + tracing::info!("handler starts"); + + let context: GetObjectContext = event.payload.get_object_context.unwrap(); + + let route = context.output_route; + let token = context.output_token; + let s3_url = context.input_s3_url; + + tracing::info!("Route: {}, s3_url: {}", route, s3_url); + + let image = client.get_file(s3_url)?; + tracing::info!("Image loaded. Length: {}", image.len()); + + let thumbnail = get_thumbnail(image, size); + tracing::info!("thumbnail created. Length: {}", thumbnail.len()); + + // It sends the thumbnail back to the user + + client.send_file(route, token, thumbnail).await + + /* + match client.send_file(route, token, thumbnail).await { + Ok(msg) => tracing::info!(msg), + Err(msg) => tracing::info!(msg) + }; + + tracing::info!("handler ends"); + + Ok(()) + */ +} + +fn get_thumbnail(vec: Vec, size: u32) -> Vec { + let reader = Cursor::new(vec); + let mut thumbnails = create_thumbnails(reader, mime::IMAGE_PNG, [ThumbnailSize::Custom((size, size))]).unwrap(); + + let thumbnail = thumbnails.pop().unwrap(); + let mut buf = Cursor::new(Vec::new()); + thumbnail.write_png(&mut buf).unwrap(); + + buf.into_inner() +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::TRACE) + // disable printing the name of the module in every log line. + .with_target(false) + // this needs to be set to false, otherwise ANSI color codes will + // show up in a confusing manner in CloudWatch logs. + .with_ansi(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let shared_config = aws_config::load_from_env().await; + let client = S3Client::new(&shared_config); + let client_ref = &client; + + let func = service_fn(move |event| async move { function_handler(event, 128, client_ref).await }); + + let _ = run(func).await; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::fs::File; + use std::io::BufReader; + use std::io::Read; + + use super::*; + use async_trait::async_trait; + use aws_lambda_events::s3::object_lambda::Configuration; + use aws_lambda_events::s3::object_lambda::HeadObjectContext; + use aws_lambda_events::s3::object_lambda::ListObjectsContext; + use aws_lambda_events::s3::object_lambda::ListObjectsV2Context; + use aws_lambda_events::s3::object_lambda::UserIdentity; + use aws_lambda_events::s3::object_lambda::UserRequest; + use aws_lambda_events::serde_json::json; + use lambda_runtime::{Context, LambdaEvent}; + use mockall::mock; + use s3::GetFile; + use s3::SendFile; + + #[tokio::test] + async fn response_is_good() { + mock! { + FakeS3Client {} + + #[async_trait] + impl GetFile for FakeS3Client { + pub fn get_file(&self, url: String) -> Result, Box>; + } + #[async_trait] + impl SendFile for FakeS3Client { + pub async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; + } + } + + let mut mock = MockFakeS3Client::new(); + + mock.expect_get_file() + .withf(|u: &String| u.eq("S3_URL")) + .returning(|_1| Ok(get_file("testdata/image.png"))); + + mock.expect_send_file() + .withf(|r: &String, t: &String, by| { + let thumbnail = get_file("testdata/thumbnail.png"); + return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == &thumbnail; + }) + .returning(|_1, _2, _3| Ok("File sent.".to_string())); + + let payload = get_s3_event(); + let context = Context::default(); + let event = LambdaEvent { payload, context }; + + let result = function_handler(event, 10, &mock).await.unwrap(); + + assert_eq!(("File sent."), result); + } + + fn get_file(name: &str) -> Vec { + let f = File::open(name); + let mut reader = BufReader::new(f.unwrap()); + let mut buffer = Vec::new(); + + reader.read_to_end(&mut buffer).unwrap(); + + return buffer; + } + + fn get_s3_event() -> S3ObjectLambdaEvent { + return S3ObjectLambdaEvent { + x_amz_request_id: ("ID".to_string()), + head_object_context: (Some(HeadObjectContext::default())), + list_objects_context: (Some(ListObjectsContext::default())), + get_object_context: (Some(GetObjectContext { + input_s3_url: ("S3_URL".to_string()), + output_route: ("O_ROUTE".to_string()), + output_token: ("O_TOKEN".to_string()), + })), + list_objects_v2_context: (Some(ListObjectsV2Context::default())), + protocol_version: ("VERSION".to_string()), + user_identity: (UserIdentity::default()), + user_request: (UserRequest::default()), + configuration: (Configuration { + access_point_arn: ("APRN".to_string()), + supporting_access_point_arn: ("SAPRN".to_string()), + payload: (json!(null)), + }), + }; + } +} diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs new file mode 100644 index 00000000..71e03ffc --- /dev/null +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -0,0 +1,90 @@ +use async_trait::async_trait; +use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; +use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use std::{error, io::Read}; + +pub trait GetFile { + fn get_file(&self, url: String) -> Result, Box>; +} + +#[async_trait] +pub trait SendFile { + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; +} + +impl GetFile for S3Client { + fn get_file(&self, url: String) -> Result, Box> { + tracing::info!("get file url {}", url); + + let resp = ureq::get(&url).call()?; + let len: usize = resp.header("Content-Length").unwrap().parse()?; + + let mut bytes: Vec = Vec::with_capacity(len); + + std::io::Read::take(resp.into_reader(), 10_000_000).read_to_end(&mut bytes)?; + + tracing::info!("got {} bytes", bytes.len()); + + Ok(bytes) + } +} + +#[async_trait] +impl SendFile for S3Client { + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result> { + tracing::info!("send file route {}, token {}, length {}", route, token, vec.len()); + + let bytes = ByteStream::from(vec); + + let write = self + .write_get_object_response() + .request_route(route) + .request_token(token) + .status_code(200) + .body(bytes) + .send() + .await; + + if write.is_err() { + let sdk_error = write.err().unwrap(); + check_error(sdk_error); + Err("WriteGetObjectResponse creation error".into()) + } else { + Ok("File sent.".to_string()) + } + } +} + +fn check_error(error: SdkError) { + match error { + SdkError::ConstructionFailure(_err) => { + tracing::info!("ConstructionFailure"); + } + SdkError::DispatchFailure(err) => { + tracing::info!("DispatchFailure"); + if err.is_io() { + tracing::info!("IO error"); + }; + if err.is_timeout() { + tracing::info!("Timeout error"); + }; + if err.is_user() { + tracing::info!("User error"); + }; + if err.is_other().is_some() { + tracing::info!("Other error"); + }; + } + SdkError::ResponseError(_err) => tracing::info!("ResponseError"), + SdkError::TimeoutError(_err) => tracing::info!("TimeoutError"), + SdkError::ServiceError(err) => { + tracing::info!("ServiceError"); + let wgore = err.into_err(); + let meta = wgore.meta(); + let code = meta.code().unwrap_or_default(); + let msg = meta.message().unwrap_or_default(); + tracing::info!("code: {}, message: {}, meta: {}", code, msg, meta); + } + _ => tracing::info!("other error"), + } +} diff --git a/examples/basic-s3-object-lambda-thumbnail/testdata/image.png b/examples/basic-s3-object-lambda-thumbnail/testdata/image.png new file mode 100644 index 0000000000000000000000000000000000000000..078d155f6bf6735eb087eb0195b3e35f9f424d04 GIT binary patch literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-UBp4!QuJ{S0SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+uenM@oty!5+IMg#M9T6{W-Ikn3DL(-uF5{ArVg(#}JFt$q5pyixWh8ngSjC85meA z7#KARcm4s&tCqM%l%ynf4NqV|ChEy=Vy|59;VQAR!XXWJ= d863$M85tR8F)&*H Date: Thu, 22 Jun 2023 07:54:56 -0700 Subject: [PATCH 010/211] Implement custom deserializer for LambdaRequest (#666) This deserializer gives us full control over the error message that we return for invalid payloads. The default message that Serde returns is usually very confusing, and it's been reported many times as something people don't understand. This code is a copy of the code that Serde generates when it expands the Deserialize macro. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-events/src/event/alb/mod.rs | 1 + lambda-events/src/event/apigw/mod.rs | 23 ++- .../src/fixtures/example-apigw-request.json | 145 +++++++++++------- lambda-http/Cargo.toml | 2 +- lambda-http/src/deserializer.rs | 117 ++++++++++++++ lambda-http/src/lib.rs | 1 + lambda-http/src/request.rs | 5 +- 8 files changed, 238 insertions(+), 58 deletions(-) create mode 100644 lambda-http/src/deserializer.rs diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b1108c63..28df6b4a 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.10.0" +version = "0.11.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 259dce23..7bb1eb7f 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct AlbTargetGroupRequest { #[serde(with = "http_method")] pub http_method: Method, diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 917f06aa..b595d825 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,6 +13,7 @@ use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayProxyRequest where T1: DeserializeOwned, @@ -118,12 +119,25 @@ where /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayV2httpRequest { + #[serde(default, rename = "type")] + pub kind: Option, + #[serde(default)] + pub method_arn: Option, + #[serde(with = "http_method", default = "default_http_method")] + pub http_method: Method, + #[serde(default)] + pub identity_source: Option, + #[serde(default)] + pub authorization_token: Option, + #[serde(default)] + pub resource: Option, #[serde(default)] pub version: Option, #[serde(default)] pub route_key: Option, - #[serde(default)] + #[serde(default, alias = "path")] pub raw_path: Option, #[serde(default)] pub raw_query_string: Option, @@ -319,6 +333,7 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] +#[serde(deny_unknown_fields)] pub struct ApiGatewayWebsocketProxyRequest where T1: DeserializeOwned, @@ -747,6 +762,10 @@ pub struct IamPolicyStatement { pub resource: Vec, } +fn default_http_method() -> Method { + Method::GET +} + #[cfg(test)] mod test { use super::*; @@ -901,6 +920,8 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + assert_eq!("REQUEST", parsed.kind.unwrap()); + assert_eq!(Method::GET, parsed.http_method); } #[test] diff --git a/lambda-events/src/fixtures/example-apigw-request.json b/lambda-events/src/fixtures/example-apigw-request.json index 570f785b..d91e9609 100644 --- a/lambda-events/src/fixtures/example-apigw-request.json +++ b/lambda-events/src/fixtures/example-apigw-request.json @@ -1,55 +1,95 @@ { "resource": "/{proxy+}", - "path": "/hello/world", - "httpMethod": "POST", - "headers": { - "Accept": "*/*", - "Accept-Encoding": "gzip, deflate", - "cache-control": "no-cache", - "CloudFront-Forwarded-Proto": "https", - "CloudFront-Is-Desktop-Viewer": "true", - "CloudFront-Is-Mobile-Viewer": "false", - "CloudFront-Is-SmartTV-Viewer": "false", - "CloudFront-Is-Tablet-Viewer": "false", - "CloudFront-Viewer-Country": "US", - "Content-Type": "application/json", - "headerName": "headerValue", - "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", - "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", - "User-Agent": "PostmanRuntime/2.4.5", - "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", - "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", - "X-Forwarded-For": "54.240.196.186, 54.182.214.83", - "X-Forwarded-Port": "443", - "X-Forwarded-Proto": "https" - }, - "multiValueHeaders": { - "Accept": ["*/*"], - "Accept-Encoding": ["gzip, deflate"], - "cache-control": ["no-cache"], - "CloudFront-Forwarded-Proto": ["https"], - "CloudFront-Is-Desktop-Viewer": ["true"], - "CloudFront-Is-Mobile-Viewer": ["false"], - "CloudFront-Is-SmartTV-Viewer": ["false"], - "CloudFront-Is-Tablet-Viewer": ["false"], - "CloudFront-Viewer-Country": ["US"], - "Content-Type": ["application/json"], - "headerName": ["headerValue"], - "Host": ["gy415nuibc.execute-api.us-east-1.amazonaws.com"], - "Postman-Token": ["9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f"], - "User-Agent": ["PostmanRuntime/2.4.5"], - "Via": ["1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)"], - "X-Amz-Cf-Id": ["pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A=="], - "X-Forwarded-For": ["54.240.196.186, 54.182.214.83"], - "X-Forwarded-Port": ["443"], - "X-Forwarded-Proto": ["https"] - }, + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, "queryStringParameters": { "name": "me" - }, - "multiValueQueryStringParameters": { - "name": ["me"] - }, + }, + "multiValueQueryStringParameters": { + "name": [ + "me" + ] + }, "pathParameters": { "proxy": "hello/world" }, @@ -70,9 +110,9 @@ "accountId": "theAccountId", "cognitoIdentityId": "theCognitoIdentityId", "caller": "theCaller", - "apiKey": "theApiKey", - "apiKeyId": "theApiKeyId", - "accessKey": "ANEXAMPLEOFACCESSKEY", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", "sourceIp": "192.168.196.186", "cognitoAuthenticationType": "theCognitoAuthenticationType", "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", @@ -92,5 +132,4 @@ "apiId": "gy415nuibc" }, "body": "{\r\n\t\"a\": 1\r\n}" -} - +} \ No newline at end of file diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index edc68650..be111092 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -40,7 +40,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.10.0" +version = "0.11.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs new file mode 100644 index 00000000..1771ea7b --- /dev/null +++ b/lambda-http/src/deserializer.rs @@ -0,0 +1,117 @@ +use crate::request::LambdaRequest; +use aws_lambda_events::{ + alb::AlbTargetGroupRequest, + apigw::{ApiGatewayProxyRequest, ApiGatewayV2httpRequest, ApiGatewayWebsocketProxyRequest}, +}; +use serde::{de::Error, Deserialize}; + +const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; + +impl<'de> Deserialize<'de> for LambdaRequest { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let content = match serde::__private::de::Content::deserialize(deserializer) { + Ok(content) => content, + Err(err) => return Err(err), + }; + #[cfg(feature = "apigw_rest")] + if let Ok(res) = + ApiGatewayProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + { + return Ok(LambdaRequest::ApiGatewayV1(res)); + } + #[cfg(feature = "apigw_http")] + if let Ok(res) = ApiGatewayV2httpRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { + return Ok(LambdaRequest::ApiGatewayV2(res)); + } + #[cfg(feature = "alb")] + if let Ok(res) = + AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + { + return Ok(LambdaRequest::Alb(res)); + } + #[cfg(feature = "apigw_websockets")] + if let Ok(res) = ApiGatewayWebsocketProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::< + D::Error, + >::new(&content)) + { + return Ok(LambdaRequest::WebSocket(res)); + } + + Err(Error::custom(ERROR_CONTEXT)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_apigw_rest() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw rest data"); + match req { + LambdaRequest::ApiGatewayV1(req) => { + assert_eq!("12345678912", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_apigw_http() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-v2-request-iam.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw http data"); + match req { + LambdaRequest::ApiGatewayV2(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_alb() { + let data = include_bytes!( + "../../lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json" + ); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze alb rest data"); + match req { + LambdaRequest::Alb(req) => { + assert_eq!( + "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh", + req.request_context.elb.target_group_arn.unwrap() + ); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_apigw_websocket() { + let data = + include_bytes!("../../lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw websocket data"); + match req { + LambdaRequest::WebSocket(req) => { + assert_eq!("CONNECT", req.request_context.event_type.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_error() { + let err = serde_json::from_str::("{\"command\": \"hi\"}").unwrap_err(); + + assert_eq!(ERROR_CONTEXT, err.to_string()); + } +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 37c167a0..bc9e753d 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -70,6 +70,7 @@ pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; use request::RequestFuture; use response::ResponseFuture; +mod deserializer; pub mod ext; pub mod request; mod response; diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 5ed3effe..ea418595 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -20,8 +20,10 @@ use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsoc use aws_lambda_events::{encodings::Body, query_map::QueryMap}; use http::header::HeaderName; use http::{HeaderMap, HeaderValue}; + use serde::{Deserialize, Serialize}; use serde_json::error::Error as JsonError; + use std::future::Future; use std::pin::Pin; use std::{env, io::Read, mem}; @@ -33,8 +35,7 @@ use url::Url; /// This is not intended to be a type consumed by crate users directly. The order /// of the variants are notable. Serde will try to deserialize in this order. #[doc(hidden)] -#[derive(Deserialize, Debug)] -#[serde(untagged)] +#[derive(Debug)] pub enum LambdaRequest { #[cfg(feature = "apigw_rest")] ApiGatewayV1(ApiGatewayProxyRequest), From 6ca8a3670d813f90a1d65b73b578d573365863c5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jul 2023 08:40:03 -0700 Subject: [PATCH 011/211] Fix APIGW path with stage (#669) * Fix APIGW path with stage APIGW HTTP has started adding the stage to the path in the event. This change checks if the stage is already a prefix in the path, and skips adding it if so. Signed-off-by: David Calavera * Add env variable to shortcircuit stage behavior. There might be cases when you don't want the runtime to do anything with paths and stages. By setting AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH in the environment, we ignore this behavior completely. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-http/src/request.rs | 44 ++++++++++++-- lambda-http/tests/data/apigw_no_host.json | 2 +- .../tests/data/apigw_proxy_request.json | 2 +- ...w_v2_proxy_request_with_stage_in_path.json | 57 +++++++++++++++++++ 4 files changed, 97 insertions(+), 8 deletions(-) create mode 100644 lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ea418595..bdb755ed 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -327,10 +327,21 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { - match stage { - None => path.into(), - Some(stage) if stage == "$default" => path.into(), - Some(stage) => format!("/{stage}{path}"), + if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() { + return path.into(); + } + + let stage = match stage { + None => return path.into(), + Some(stage) if stage == "$default" => return path.into(), + Some(stage) => stage, + }; + + let prefix = format!("/{stage}/"); + if path.starts_with(&prefix) { + path.into() + } else { + format!("/{stage}{path}") } } @@ -531,7 +542,7 @@ mod tests { assert_eq!(req.method(), "GET"); assert_eq!( req.uri(), - "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/test/hello?name=me" + "https://wt6mne2s9k.execute-api.us-west-2.amazonaws.com/test/hello?name=me" ); // Ensure this is an APIGW request @@ -733,7 +744,7 @@ mod tests { ); let req = result.expect("failed to parse request"); assert_eq!(req.method(), "GET"); - assert_eq!(req.uri(), "/test/test/hello?name=me"); + assert_eq!(req.uri(), "/test/hello?name=me"); } #[test] @@ -768,4 +779,25 @@ mod tests { let url = build_request_uri("/path with spaces/and multiple segments", &HeaderMap::new(), None, None); assert_eq!("/path%20with%20spaces/and%20multiple%20segments", url); } + + #[test] + fn deserializes_apigw_http_request_with_stage_in_path() { + let input = include_str!("../tests/data/apigw_v2_proxy_request_with_stage_in_path.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + assert_eq!("/Prod/my/path", req.uri().path()); + assert_eq!("/Prod/my/path", req.raw_http_path()); + } + + #[test] + fn test_apigw_path_with_stage() { + assert_eq!("/path", apigw_path_with_stage(&None, "/path")); + assert_eq!("/path", apigw_path_with_stage(&Some("$default".into()), "/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path")); + assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path")); + } } diff --git a/lambda-http/tests/data/apigw_no_host.json b/lambda-http/tests/data/apigw_no_host.json index 3143c81b..78a40dee 100644 --- a/lambda-http/tests/data/apigw_no_host.json +++ b/lambda-http/tests/data/apigw_no_host.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_proxy_request.json b/lambda-http/tests/data/apigw_proxy_request.json index 3b7cc9d2..61183846 100644 --- a/lambda-http/tests/data/apigw_proxy_request.json +++ b/lambda-http/tests/data/apigw_proxy_request.json @@ -1,5 +1,5 @@ { - "path": "/test/hello", + "path": "/hello", "headers": { "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Encoding": "gzip, deflate, lzma, sdch, br", diff --git a/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json new file mode 100644 index 00000000..86e173c6 --- /dev/null +++ b/lambda-http/tests/data/apigw_v2_proxy_request_with_stage_in_path.json @@ -0,0 +1,57 @@ +{ + "version": "2.0", + "routeKey": "Prod", + "rawPath": "/Prod/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": [ + "cookie1=value1", + "cookie2=value2" + ], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/Prod/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "Prod", + "stage": "Prod", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "body": "Hello from Lambda", + "pathParameters": { + "parameter1": "value1" + }, + "isBase64Encoded": false, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + } +} \ No newline at end of file From a00fe5d386d6700fb6e9bc92ae30bb2513f41d7e Mon Sep 17 00:00:00 2001 From: BMorinDrifter <108374412+BMorinDrifter@users.noreply.github.com> Date: Fri, 7 Jul 2023 11:11:08 -0700 Subject: [PATCH 012/211] Fix check examples workflow to only run once in pull requests (#673) --- .github/workflows/check-examples.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml index ba7bc709..5ef1536a 100644 --- a/.github/workflows/check-examples.yml +++ b/.github/workflows/check-examples.yml @@ -1,6 +1,9 @@ name: Check examples -on: [push, pull_request] +on: + push: + branches: [main] + pull_request: jobs: check: From 2b7d1619750d569a6876a4733d98992a02722fb6 Mon Sep 17 00:00:00 2001 From: BMorinDrifter <108374412+BMorinDrifter@users.noreply.github.com> Date: Sun, 9 Jul 2023 11:09:19 -0700 Subject: [PATCH 013/211] Exclude the ansi feature of tracing-subscriber instead of calling .with_ansi(false) in the builder (#674) * Disable the ansi feature for tracing_subscriber instead of building with_ansi(false) * use tracing::Level::INFO instead of via tracing-subscriber in README --- .../advanced-sqs-partial-batch-failures/Cargo.toml | 2 +- .../advanced-sqs-partial-batch-failures/src/main.rs | 11 ++--------- examples/basic-error-handling/Cargo.toml | 2 +- examples/basic-error-handling/src/main.rs | 3 --- examples/basic-lambda-external-runtime/Cargo.toml | 2 +- examples/basic-lambda-external-runtime/src/main.rs | 3 --- examples/basic-lambda/Cargo.toml | 2 +- examples/basic-lambda/src/main.rs | 3 --- examples/basic-s3-object-lambda-thumbnail/Cargo.toml | 2 +- .../basic-s3-object-lambda-thumbnail/src/main.rs | 3 --- examples/basic-s3-thumbnail/Cargo.toml | 2 +- examples/basic-s3-thumbnail/src/main.rs | 3 --- examples/basic-sdk/Cargo.toml | 2 +- examples/basic-sdk/src/main.rs | 3 --- examples/basic-shared-resource/Cargo.toml | 3 +-- examples/basic-shared-resource/src/main.rs | 3 --- examples/basic-sqs/Cargo.toml | 2 +- examples/basic-sqs/src/main.rs | 3 --- examples/basic-streaming-response/Cargo.toml | 2 +- examples/basic-streaming-response/src/main.rs | 3 --- examples/extension-basic/Cargo.toml | 2 +- examples/extension-basic/src/main.rs | 3 --- examples/extension-combined/Cargo.toml | 2 +- examples/extension-combined/src/main.rs | 3 --- examples/extension-custom-events/Cargo.toml | 2 +- examples/extension-custom-events/src/main.rs | 3 --- examples/extension-custom-service/Cargo.toml | 2 +- examples/extension-custom-service/src/main.rs | 3 --- examples/extension-logs-basic/Cargo.toml | 2 +- examples/extension-logs-basic/src/main.rs | 3 --- examples/extension-logs-custom-service/Cargo.toml | 2 +- examples/extension-logs-custom-service/src/main.rs | 3 --- examples/extension-logs-kinesis-firehose/Cargo.toml | 2 +- examples/extension-logs-kinesis-firehose/src/main.rs | 3 --- examples/extension-telemetry-basic/Cargo.toml | 2 +- examples/extension-telemetry-basic/src/main.rs | 3 --- examples/http-axum-diesel/Cargo.toml | 2 +- examples/http-axum-diesel/src/main.rs | 3 --- examples/http-axum/Cargo.toml | 2 +- examples/http-axum/src/main.rs | 3 --- examples/http-basic-lambda/Cargo.toml | 2 +- examples/http-basic-lambda/src/main.rs | 3 --- examples/http-cors/Cargo.toml | 2 +- examples/http-cors/src/main.rs | 3 --- examples/http-dynamodb/Cargo.toml | 2 +- examples/http-dynamodb/src/main.rs | 3 --- examples/http-query-parameters/Cargo.toml | 2 +- examples/http-query-parameters/src/main.rs | 3 --- examples/http-raw-path/Cargo.toml | 2 +- examples/http-raw-path/src/main.rs | 3 --- examples/http-shared-resource/Cargo.toml | 2 +- examples/http-shared-resource/src/main.rs | 3 --- examples/http-tower-trace/Cargo.toml | 2 +- examples/http-tower-trace/src/main.rs | 3 --- lambda-http/README.md | 12 ++++-------- lambda-integration-tests/Cargo.toml | 2 +- lambda-integration-tests/src/bin/extension-fn.rs | 3 --- lambda-integration-tests/src/bin/extension-trait.rs | 3 --- lambda-integration-tests/src/bin/http-fn.rs | 3 --- lambda-integration-tests/src/bin/http-trait.rs | 3 --- lambda-integration-tests/src/bin/logs-trait.rs | 3 --- lambda-integration-tests/src/bin/runtime-fn.rs | 3 --- lambda-integration-tests/src/bin/runtime-trait.rs | 3 --- 63 files changed, 34 insertions(+), 145 deletions(-) diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 0dfe49d9..04158320 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -13,4 +13,4 @@ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } futures = "0.3" tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 2923d2e4..23faa68f 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -33,9 +33,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); @@ -78,9 +75,7 @@ where tracing::trace!("Handling batch size {}", event.payload.records.len()); let create_task = |msg| { // We need to keep the message_id to report failures to SQS - let SqsMessageObj { - message_id, body, .. - } = msg; + let SqsMessageObj { message_id, body, .. } = msg; let span = tracing::span!(tracing::Level::INFO, "Handling SQS msg", message_id); let task = async { //TODO catch panics like the `run` function from lambda_runtime @@ -104,9 +99,7 @@ where } }, ) - .map(|id| BatchItemFailure { - item_identifier: id, - }) + .map(|id| BatchItemFailure { item_identifier: id }) .collect(); Ok(SqsBatchResponse { diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index 325b08e1..e8699141 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -17,6 +17,6 @@ serde_json = "1.0.81" simple-error = "0.2.3" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 8d317a24..0939d2d0 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -54,9 +54,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index 9c732b2f..0682efaf 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -12,4 +12,4 @@ serde = "1.0.163" tokio = "1.28.2" tokio-test = "0.4.2" tracing = "0.1.37" -tracing-subscriber = "0.3.17" +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index 71bd123b..9419b17b 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -29,9 +29,6 @@ fn main() -> Result<(), io::Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index fd6bd5b2..cd2efa42 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -15,5 +15,5 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index 2bb4aeb3..09145bb3 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -29,9 +29,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index 493846ad..79640cc2 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -20,7 +20,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 7786f56e..771a829c 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -69,9 +69,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::TRACE) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index dc5e67f8..7c788a00 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -20,7 +20,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.54.1" aws-sdk-s3 = "0.24.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index f9cb0716..d996de0c 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -111,9 +111,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 0ffea2da..0e930f7c 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -13,7 +13,7 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index 5838d7c8..6e2654a4 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -38,9 +38,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index 25637976..b3e2faa5 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -15,6 +15,5 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } - +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-shared-resource/src/main.rs b/examples/basic-shared-resource/src/main.rs index 15c38741..15ababa0 100644 --- a/examples/basic-shared-resource/src/main.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -49,9 +49,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index a1b11567..9d259218 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -20,4 +20,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs index 319e4519..63967893 100644 --- a/examples/basic-sqs/src/main.rs +++ b/examples/basic-sqs/src/main.rs @@ -25,9 +25,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index fc284674..4bbe66f4 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -14,5 +14,5 @@ hyper = { version = "0.14", features = [ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } serde_json = "1.0" \ No newline at end of file diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 04c7f8ec..d90ebd33 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -30,9 +30,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index 94ee4926..caf0818c 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-basic/src/main.rs b/examples/extension-basic/src/main.rs index 4af6a47f..f9838c6b 100644 --- a/examples/extension-basic/src/main.rs +++ b/examples/extension-basic/src/main.rs @@ -19,9 +19,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index e585516a..d776f488 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-combined/src/main.rs b/examples/extension-combined/src/main.rs index 60d0f9e1..e05b1b7d 100644 --- a/examples/extension-combined/src/main.rs +++ b/examples/extension-combined/src/main.rs @@ -34,9 +34,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index 90c5d322..a826a137 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-custom-events/src/main.rs b/examples/extension-custom-events/src/main.rs index b7574642..1d39e20f 100644 --- a/examples/extension-custom-events/src/main.rs +++ b/examples/extension-custom-events/src/main.rs @@ -21,9 +21,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index 5396c137..c9ff789a 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-custom-service/src/main.rs b/examples/extension-custom-service/src/main.rs index fd85c91d..ec8ca68f 100644 --- a/examples/extension-custom-service/src/main.rs +++ b/examples/extension-custom-service/src/main.rs @@ -38,9 +38,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index 30c09117..d1983db8 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-logs-basic/src/main.rs b/examples/extension-logs-basic/src/main.rs index 5543dec9..77065cca 100644 --- a/examples/extension-logs-basic/src/main.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -20,9 +20,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index 35a9e05d..cbbe20f6 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-logs-custom-service/src/main.rs b/examples/extension-logs-custom-service/src/main.rs index 9137c017..ebe1330d 100644 --- a/examples/extension-logs-custom-service/src/main.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -61,9 +61,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index 547ad48e..0e056b1c 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -13,7 +13,7 @@ edition = "2021" lambda-extension = { path = "../../lambda-extension" } tokio = { version = "1.17.0", features = ["full"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.13.0" aws-sdk-firehose = "0.13.0" diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 68c9421c..8586e1a9 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -58,9 +58,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index bc426b68..869b604d 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -15,6 +15,6 @@ lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs index f522808c..03974bf6 100644 --- a/examples/extension-telemetry-basic/src/main.rs +++ b/examples/extension-telemetry-basic/src/main.rs @@ -45,9 +45,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index dd37346f..5a97cfab 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -20,4 +20,4 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index 227e23dd..bb47152d 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -97,9 +97,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 6a0e8905..50db3ebf 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -15,7 +15,7 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } axum = "0.6.4" serde_json = "1.0" diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 7770d861..c2805be1 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -42,9 +42,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index ad53161d..1a218330 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index 88db4886..5794dc8b 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -24,9 +24,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 65df64d4..9fd7f25b 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -16,6 +16,6 @@ lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.3", features = ["cors"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index ea1f0372..e60fb441 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -10,9 +10,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index 6b6a0205..c3f6d8be 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -20,6 +20,6 @@ aws-sdk-dynamodb = "0.21.0" aws-config = "0.51.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 6a0c8947..5a7030f9 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -59,9 +59,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 5089a85e..7aeb1189 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 0300df37..e189d12d 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -27,9 +27,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index 4cb3e23f..f4060428 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index 3694e61b..7fa6e6d5 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -7,9 +7,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 4d655489..207f253b 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -15,6 +15,6 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index 16493452..d76ccec4 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -17,9 +17,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index bf4a5b0c..2b8f7a60 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -16,4 +16,4 @@ lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.3.4", features = ["trace"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index 678d79cd..072f8256 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -14,9 +14,6 @@ async fn main() -> Result<(), Error> { .with_max_level(tracing::Level::INFO) // disable printing the name of the module in every log line. .with_target(false) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-http/README.md b/lambda-http/README.md index 161a5576..464be88b 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -50,9 +50,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -89,9 +88,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -131,9 +129,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); run(service_fn(function_handler)).await @@ -191,9 +188,8 @@ use serde_json::json; #[tokio::main] async fn main() -> Result<(), Error> { tracing_subscriber::fmt() - .with_ansi(false) .without_time() - .with_max_level(tracing_subscriber::filter::LevelFilter::INFO) + .with_max_level(tracing::Level::INFO) .init(); let config = aws_config::from_env() diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 8dd28f0a..1b0fc3ef 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -19,4 +19,4 @@ lambda-extension = { path = "../lambda-extension" } serde = { version = "1", features = ["derive"] } tokio = { version = "1", features = ["full"] } tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = "0.3" +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs index ea5fc26c..5e9ec553 100644 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ b/lambda-integration-tests/src/bin/extension-fn.rs @@ -20,9 +20,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs index ecf46c81..e2c73fa3 100644 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ b/lambda-integration-tests/src/bin/extension-trait.rs @@ -80,9 +80,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs index cd252280..8107f423 100644 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ b/lambda-integration-tests/src/bin/http-fn.rs @@ -17,9 +17,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs index 765b0d66..d8e6f74f 100644 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ b/lambda-integration-tests/src/bin/http-trait.rs @@ -75,9 +75,6 @@ impl Service for MyHandler { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs index 3f5a4909..b474bc8d 100644 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ b/lambda-integration-tests/src/bin/logs-trait.rs @@ -64,9 +64,6 @@ impl Service> for MyLogsProcessor { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs index 1b3f3e0d..d16717aa 100644 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ b/lambda-integration-tests/src/bin/runtime-fn.rs @@ -28,9 +28,6 @@ async fn main() -> Result<(), Error> { // While `tracing` is used internally, `log` can be used as well if preferred. tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs index b925e138..0bf31e43 100644 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ b/lambda-integration-tests/src/bin/runtime-trait.rs @@ -84,9 +84,6 @@ impl Service> for MyHandler { async fn main() -> Result<(), Error> { tracing_subscriber::fmt() .with_max_level(tracing::Level::INFO) - // this needs to be set to false, otherwise ANSI color codes will - // show up in a confusing manner in CloudWatch logs. - .with_ansi(false) // disabling time is handy because CloudWatch will add the ingestion time. .without_time() .init(); From 33cce7745373685fe4e615aa7621fe3272c0f371 Mon Sep 17 00:00:00 2001 From: Peter Borkuti Date: Mon, 10 Jul 2023 16:53:55 +0200 Subject: [PATCH 014/211] Fix s3 tests for s3 examples (#675) * upgrade basic-s3-thumbnail example + refactor test I noticed that I am testing thumbnailer module when I compared the generated thumbnail with a stored one. This can lead test failures. So I mocked the thumbnail generation. * refactor test for basic-s3-object-lambda example --------- Co-authored-by: borkupe --- .../src/main.rs | 61 +++++++----------- examples/basic-s3-thumbnail/Cargo.toml | 13 ++-- examples/basic-s3-thumbnail/README.md | 16 +++++ examples/basic-s3-thumbnail/src/main.rs | 44 ++++++------- examples/basic-s3-thumbnail/src/s3.rs | 4 +- .../basic-s3-thumbnail/testdata/image.png | Bin 282 -> 0 bytes .../basic-s3-thumbnail/testdata/thumbnail.png | Bin 82 -> 0 bytes 7 files changed, 71 insertions(+), 67 deletions(-) delete mode 100644 examples/basic-s3-thumbnail/testdata/image.png delete mode 100644 examples/basic-s3-thumbnail/testdata/thumbnail.png diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 771a829c..328e7500 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -1,10 +1,9 @@ -use std::{error, io::Cursor}; +use std::error; use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; use aws_sdk_s3::Client as S3Client; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use s3::{GetFile, SendFile}; -use thumbnailer::{create_thumbnails, ThumbnailSize}; mod s3; @@ -35,28 +34,21 @@ pub(crate) async fn function_handler( let thumbnail = get_thumbnail(image, size); tracing::info!("thumbnail created. Length: {}", thumbnail.len()); - // It sends the thumbnail back to the user - client.send_file(route, token, thumbnail).await - - /* - match client.send_file(route, token, thumbnail).await { - Ok(msg) => tracing::info!(msg), - Err(msg) => tracing::info!(msg) - }; - - tracing::info!("handler ends"); - - Ok(()) - */ } +#[cfg(not(test))] fn get_thumbnail(vec: Vec, size: u32) -> Vec { - let reader = Cursor::new(vec); - let mut thumbnails = create_thumbnails(reader, mime::IMAGE_PNG, [ThumbnailSize::Custom((size, size))]).unwrap(); + let reader = std::io::Cursor::new(vec); + let mut thumbnails = thumbnailer::create_thumbnails( + reader, + mime::IMAGE_PNG, + [thumbnailer::ThumbnailSize::Custom((size, size))], + ) + .unwrap(); let thumbnail = thumbnails.pop().unwrap(); - let mut buf = Cursor::new(Vec::new()); + let mut buf = std::io::Cursor::new(Vec::new()); thumbnail.write_png(&mut buf).unwrap(); buf.into_inner() @@ -85,11 +77,17 @@ async fn main() -> Result<(), Error> { } #[cfg(test)] -mod tests { - use std::fs::File; - use std::io::BufReader; - use std::io::Read; +fn get_thumbnail(vec: Vec, _size: u32) -> Vec { + let s = unsafe { std::str::from_utf8_unchecked(&vec) }; + match s { + "IMAGE" => "THUMBNAIL".into(), + _ => "Input is not IMAGE".into(), + } +} + +#[cfg(test)] +mod tests { use super::*; use async_trait::async_trait; use aws_lambda_events::s3::object_lambda::Configuration; @@ -122,13 +120,12 @@ mod tests { let mut mock = MockFakeS3Client::new(); mock.expect_get_file() - .withf(|u: &String| u.eq("S3_URL")) - .returning(|_1| Ok(get_file("testdata/image.png"))); + .withf(|u| u.eq("S3_URL")) + .returning(|_1| Ok("IMAGE".into())); mock.expect_send_file() - .withf(|r: &String, t: &String, by| { - let thumbnail = get_file("testdata/thumbnail.png"); - return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == &thumbnail; + .withf(|r, t, by| { + return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes(); }) .returning(|_1, _2, _3| Ok("File sent.".to_string())); @@ -141,16 +138,6 @@ mod tests { assert_eq!(("File sent."), result); } - fn get_file(name: &str) -> Vec { - let f = File::open(name); - let mut reader = BufReader::new(f.unwrap()); - let mut buffer = Vec::new(); - - reader.read_to_end(&mut buffer).unwrap(); - - return buffer; - } - fn get_s3_event() -> S3ObjectLambdaEvent { return S3ObjectLambdaEvent { x_amz_request_id: ("ID".to_string()), diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 7c788a00..6bbe11b7 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -20,14 +20,15 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } -aws-config = "0.54.1" -aws-sdk-s3 = "0.24.0" +tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } +aws-config = "0.55" +aws-smithy-http = "0.55.3" +aws-sdk-s3 = "0.28" thumbnailer = "0.4.0" mime = "0.3.16" -async-trait = "0.1.66" +async-trait = "0.1.68" [dev-dependencies] -mockall = "0.11.3" -tokio-test = "0.4.2" +mockall = "0.11" +tokio-test = "0.4" chrono = "0.4" diff --git a/examples/basic-s3-thumbnail/README.md b/examples/basic-s3-thumbnail/README.md index de2d56f8..000874cc 100644 --- a/examples/basic-s3-thumbnail/README.md +++ b/examples/basic-s3-thumbnail/README.md @@ -5,6 +5,22 @@ it downloads the created file, generates a thumbnail from it (it assumes that the file is an image) and uploads it to S3 into a bucket named [original-bucket-name]-thumbs. +## Set up +1. Create a lambda function and upload the bootloader.zip +2. Go to aws services S3 +3. Create a bucket, let's say with name bucketx +4. Create another bucket bucketx-thumbs +5. Got to the bucketx properties tab, event notifications +6. Create lambda event notification for "all object create event" and select your lambda function +7. Go to the lambda function, configuration and open the role name +8. Add "AmazonS3FullAccess" permission + +## Test + +1. Go to S3 and upload a png picture into bucketx. Beware to not have spaces or any special characters in the file name +2. Go to S3 bucketx-thumbs and check if an image is created there. + + ## Build & Deploy 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d996de0c..d92c822b 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -1,10 +1,7 @@ -use std::io::Cursor; - use aws_lambda_events::{event::s3::S3Event, s3::S3EventRecord}; use aws_sdk_s3::Client as S3Client; use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use s3::{GetFile, PutFile}; -use thumbnailer::{create_thumbnails, ThumbnailSize}; mod s3; @@ -86,7 +83,12 @@ fn get_file_props(record: S3EventRecord) -> Result<(String, String), String> { Ok((bucket, key)) } +#[cfg(not(test))] fn get_thumbnail(vec: Vec, size: u32) -> Result, String> { + use std::io::Cursor; + + use thumbnailer::{create_thumbnails, ThumbnailSize}; + let reader = Cursor::new(vec); let mime = mime::IMAGE_PNG; let sizes = [ThumbnailSize::Custom((size, size))]; @@ -126,12 +128,19 @@ async fn main() -> Result<(), Error> { Ok(()) } +#[cfg(test)] +fn get_thumbnail(vec: Vec, _size: u32) -> Result, String> { + let s = unsafe { std::str::from_utf8_unchecked(&vec) }; + + match s { + "IMAGE" => Ok("THUMBNAIL".into()), + _ => Err("Input is not IMAGE".to_string()), + } +} + #[cfg(test)] mod tests { use std::collections::HashMap; - use std::fs::File; - use std::io::BufReader; - use std::io::Read; use super::*; use async_trait::async_trait; @@ -141,7 +150,7 @@ mod tests { use aws_lambda_events::s3::S3Object; use aws_lambda_events::s3::S3RequestParameters; use aws_lambda_events::s3::S3UserIdentity; - use aws_sdk_s3::error::GetObjectError; + use aws_sdk_s3::operation::get_object::GetObjectError; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; use s3::GetFile; @@ -171,15 +180,14 @@ mod tests { let mut mock = MockFakeS3Client::new(); mock.expect_get_file() - .withf(|b: &str, k: &str| b.eq(bucket) && k.eq(key)) - .returning(|_1, _2| Ok(get_file("testdata/image.png"))); + .withf(|b, k| b.eq(bucket) && k.eq(key)) + .returning(|_1, _2| Ok("IMAGE".into())); mock.expect_put_file() - .withf(|bu: &str, ke: &str, by| { - let thumbnail = get_file("testdata/thumbnail.png"); - return bu.eq("test-bucket-thumbs") && ke.eq(key) && by == &thumbnail; + .withf(|bu, ke, by| { + return bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes()); }) - .returning(|_1, _2, _3| Ok("Done".to_string())); + .return_const(Ok("Done".to_string())); let payload = get_s3_event("ObjectCreated", bucket, key); let event = LambdaEvent { payload, context }; @@ -189,16 +197,6 @@ mod tests { assert_eq!((), result); } - fn get_file(name: &str) -> Vec { - let f = File::open(name); - let mut reader = BufReader::new(f.unwrap()); - let mut buffer = Vec::new(); - - reader.read_to_end(&mut buffer).unwrap(); - - return buffer; - } - fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { return S3Event { records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 83ef7bc7..17d7f975 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -1,5 +1,7 @@ use async_trait::async_trait; -use aws_sdk_s3::{error::GetObjectError, types::ByteStream, Client as S3Client}; +use aws_sdk_s3::operation::get_object::GetObjectError; +use aws_sdk_s3::Client as S3Client; +use aws_smithy_http::byte_stream::ByteStream; #[async_trait] pub trait GetFile { diff --git a/examples/basic-s3-thumbnail/testdata/image.png b/examples/basic-s3-thumbnail/testdata/image.png deleted file mode 100644 index 078d155f6bf6735eb087eb0195b3e35f9f424d04..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmeAS@N?(olHy`uVBq!ia0vp^DIm-UBp4!QuJ{S0SkfJR9T^xl_H+M9WCijSl0AZa z85pY67#JE_7#My5g&JNkFq9fFFuY1&V6d9Oz#v{QXIG#NP=cu>$S;_Ip=|P53lJ~K z+uenM@oty!5+IMg#M9T6{W-Ikn3DL(-uF5{ArVg(#}JFt$q5pyixWh8ngSjC85meA z7#KARcm4s&tCqM%l%ynf4NqV|ChEy=Vy|59;VQAR!XXWJ= d863$M85tR8F)&*H Date: Sun, 16 Jul 2023 23:53:01 +0300 Subject: [PATCH 015/211] Serialize APIGW queryStringParameters properly (#676) Reference: https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html 'queryStringParameters' were serialized incorrectly to format like queryStringParameters:{"key":["value"]} when it should be like queryStringParameters:{"key":"value"} --- lambda-events/Cargo.toml | 2 +- lambda-events/src/event/apigw/mod.rs | 30 ++++ ...-apigw-request-multi-value-parameters.json | 136 ++++++++++++++++++ ...igw-v2-request-multi-value-parameters.json | 61 ++++++++ 4 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 28df6b4a..27b577cb 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -30,7 +30,7 @@ chrono = { version = "0.4.23", default-features = false, features = [ "serde", "std", ], optional = true } -query_map = { version = "^0.6", features = ["serde", "url-query"], optional = true } +query_map = { version = "^0.7", features = ["serde", "url-query"], optional = true } flate2 = { version = "1.0.24", optional = true } [features] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index b595d825..9119bdc7 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -34,6 +34,7 @@ where #[serde(serialize_with = "serialize_multi_value_headers")] pub multi_value_headers: HeaderMap, #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + #[serde(serialize_with = "query_map::serde::aws_api_gateway_v1::serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] pub multi_value_query_string_parameters: QueryMap, @@ -151,6 +152,7 @@ pub struct ApiGatewayV2httpRequest { deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" )] #[serde(skip_serializing_if = "QueryMap::is_empty")] + #[serde(serialize_with = "query_map::serde::aws_api_gateway_v2::serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] @@ -832,6 +834,21 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request_multi_value_parameters() { + let data = include_bytes!("../../fixtures/example-apigw-request-multi-value-parameters.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert!(output.contains(r#""multiValueQueryStringParameters":{"name":["me","me2"]}"#)); + assert!(output.contains(r#""queryStringParameters":{"name":"me"}"#)); + assert!(output.contains(r#""headername":["headerValue","headerValue2"]"#)); + assert!(output.contains(r#""headername":"headerValue2""#)); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_restapi_openapi_request() { @@ -872,6 +889,19 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_multi_value_parameters() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-multi-value-parameters.json"); + let parsed: ApiGatewayV2httpRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2httpRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert!(output.contains(r#""header2":"value1,value2""#)); + assert!(output.contains(r#""queryStringParameters":{"Parameter1":"value1,value2"}"#)); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_v2_request_no_authorizer() { diff --git a/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json b/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json new file mode 100644 index 00000000..340b1df1 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request-multi-value-parameters.json @@ -0,0 +1,136 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue2", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue", + "headerValue2" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": [ + "me", "me2" + ] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser" + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json b/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json new file mode 100644 index 00000000..5094d6c2 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-multi-value-parameters.json @@ -0,0 +1,61 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "Parameter1=value1&Parameter1=value2", + "cookies": [ + "cookie1", + "cookie2" + ], + "headers": { + "Header2": "value1,value2" + }, + "queryStringParameters": { + "Parameter1": "value1,value2" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "lambda": { + "key": "value" + } + }, + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "-----BEGIN CERTIFICATE-----\nMIIEZTCCAk0CAQEwDQ...", + "issuerDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Private CA", + "serialNumber": "1", + "subjectDN": "C=US,ST=Washington,L=Seattle,O=Amazon Web Services,OU=Security,CN=My Client", + "validity": { + "notAfter": "Aug 5 00:28:21 2120 GMT", + "notBefore": "Aug 29 00:28:21 2020 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "time": "12/Mar/2020:19:03:58+0000", + "timeEpoch": 1583348638390, + "http": { + "method": "GET", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + } + }, + "stageVariables": { + "stageVariable1": "value1", + "stageVariable2": "value2" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "isBase64Encoded": false +} + From b7f198d4437470273a751f4c12ad153a5049149b Mon Sep 17 00:00:00 2001 From: sumagowda Date: Mon, 31 Jul 2023 08:26:55 -0700 Subject: [PATCH 016/211] Example that uses Axum and Diesel to connect to a PostgreSQL database with SSLmode ON (#682) Co-authored-by: Suma Gowda --- examples/http-axum-diesel-ssl/.DS_Store | Bin 0 -> 6148 bytes examples/http-axum-diesel-ssl/Cargo.toml | 28 +++ examples/http-axum-diesel-ssl/README.md | 13 ++ .../2023-04-07-231632_create_posts/down.sql | 2 + .../2023-04-07-231632_create_posts/up.sql | 7 + examples/http-axum-diesel-ssl/src/main.rs | 165 ++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 examples/http-axum-diesel-ssl/.DS_Store create mode 100755 examples/http-axum-diesel-ssl/Cargo.toml create mode 100755 examples/http-axum-diesel-ssl/README.md create mode 100755 examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql create mode 100755 examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql create mode 100755 examples/http-axum-diesel-ssl/src/main.rs diff --git a/examples/http-axum-diesel-ssl/.DS_Store b/examples/http-axum-diesel-ssl/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..06fbd0f6d1e6ab54258b97a348681bc810b1d5ae GIT binary patch literal 6148 zcmeHK%}T>S5Z-O8O({YS3Oz1(Etp6_#Y>3w1&ruHr6#6mFlI}V+CwSitS{t~_&m<+ zZop#BB6bFLzxmzGevtiPjB$S+yNub4F$)?ZN2Ni~-56?_WJHc*6tgIm5m+D5%*6gW z;J3F~!E*KxExvz$niQqueDYSiv$fr|x>n!13!da6D8q7I_+fs7)}@qbRO?}Ml}r~C zd-qIcWte2sTooko6jE-llPs1CU(U0*P_=;$SUsyZu?NfL$T=Pg*L7Ayu{;@#hT`bZ zU9Ec7-u}Vq#pF4C$>f`+lLOmIb_~|=4vJaLYmjHD%pSp0XV+PT!~iis3=jjG&44)< zto~-xK&vMPh=B$MaDNcc5IuvHMzwW7hu3F}cMwrP$F~HcFz6YqG(rS~>ry~n%FPpl z>vHf5ljj+%H0pB3)yy!CnYn(va5X#lg-U1K(?~rrKn$!i(AK7n=l?nUGL4V?^%Am( z0b<~vF~B?HVC=)9%-Q;5d3e?eX!p=iFt0)d1oX8_02sKB3{+6Z1?rIJ8LTwoDCk$^ PfOHX1giuEe`~m}CkhDpD literal 0 HcmV?d00001 diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml new file mode 100755 index 00000000..cdcdd4ef --- /dev/null +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "http-axum-diesel" +version = "0.1.0" +edition = "2021" + + +# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to manage dependencies. +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +axum = "0.6.4" +bb8 = "0.8.0" +diesel = "2.0.3" +diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.159" +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +futures-util = "0.3.21" +rustls = "0.20.8" +rustls-native-certs = "0.6.2" +tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio-postgres = "0.7.7" +tokio-postgres-rustls = "0.9.0" \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/README.md b/examples/http-axum-diesel-ssl/README.md new file mode 100755 index 00000000..8b2330f5 --- /dev/null +++ b/examples/http-axum-diesel-ssl/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function example + +This example shows how to develop a REST API with Axum and Diesel that connects to a Postgres database. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql new file mode 100755 index 00000000..e00da655 --- /dev/null +++ b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE posts \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql new file mode 100755 index 00000000..aa684de6 --- /dev/null +++ b/examples/http-axum-diesel-ssl/migrations/2023-04-07-231632_create_posts/up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +CREATE TABLE posts ( + id SERIAL PRIMARY KEY, + title VARCHAR NOT NULL, + content TEXT NOT NULL, + published BOOLEAN NOT NULL DEFAULT FALSE +) \ No newline at end of file diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs new file mode 100755 index 00000000..c2f6b933 --- /dev/null +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -0,0 +1,165 @@ +use diesel::{ConnectionError, ConnectionResult}; +use futures_util::future::BoxFuture; +use futures_util::FutureExt; +use std::time::Duration; + +use axum::{ + extract::{Path, State}, + response::Json, + routing::get, + Router, +}; +use bb8::Pool; +use diesel::prelude::*; +use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use lambda_http::{http::StatusCode, run, Error}; +use serde::{Deserialize, Serialize}; + +table! { + posts (id) { + id -> Integer, + title -> Text, + content -> Text, + published -> Bool, + } +} + +#[derive(Default, Queryable, Selectable, Serialize)] +struct Post { + id: i32, + title: String, + content: String, + published: bool, +} + +#[derive(Deserialize, Insertable)] +#[diesel(table_name = posts)] +struct NewPost { + title: String, + content: String, + published: bool, +} + +type AsyncPool = Pool>; +type ServerError = (StatusCode, String); + +async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = diesel::insert_into(posts::table) + .values(post) + .returning(Post::as_returning()) + .get_result(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn list_posts(State(pool): State) -> Result>, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let posts = posts::table + .filter(posts::dsl::published.eq(true)) + .load(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(posts)) +} + +async fn get_post(State(pool): State, Path(post_id): Path) -> Result, ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + let post = posts::table + .find(post_id) + .first(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(Json(post)) +} + +async fn delete_post(State(pool): State, Path(post_id): Path) -> Result<(), ServerError> { + let mut conn = pool.get().await.map_err(internal_server_error)?; + + diesel::delete(posts::table.find(post_id)) + .execute(&mut conn) + .await + .map_err(internal_server_error)?; + + Ok(()) +} + +fn internal_server_error(err: E) -> ServerError { + (StatusCode::INTERNAL_SERVER_ERROR, err.to_string()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // required to enable CloudWatch error logging by the runtime + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + // Set up the database connection + // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require + let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); + + let mgr = AsyncDieselConnectionManager::::new_with_setup( + db_url, + establish_connection, + ); + + let pool = Pool::builder() + .max_size(10) + .min_idle(Some(5)) + .max_lifetime(Some(Duration::from_secs(60 * 60 * 24))) + .idle_timeout(Some(Duration::from_secs(60 * 2))) + .build(mgr) + .await?; + + // Set up the API routes + let posts_api = Router::new() + .route("/", get(list_posts).post(create_post)) + .route("/:id", get(get_post).delete(delete_post)) + .route("/get", get(list_posts)) + .route("/get/:id", get(get_post)); + let app = Router::new().nest("/posts", posts_api).with_state(pool); + + run(app).await +} + + +fn establish_connection(config: &str) -> BoxFuture> { + let fut = async { + // We first set up the way we want rustls to work. + let rustls_config = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_certs()) + .with_no_client_auth(); + let tls = tokio_postgres_rustls::MakeRustlsConnect::new(rustls_config); + let (client, conn) = tokio_postgres::connect(config, tls) + .await + .map_err(|e| ConnectionError::BadConnection(e.to_string()))?; + tokio::spawn(async move { + if let Err(e) = conn.await { + eprintln!("Database connection: {e}"); + } + }); + AsyncPgConnection::try_from(client).await + }; + fut.boxed() +} + +fn root_certs() -> rustls::RootCertStore { + let mut roots = rustls::RootCertStore::empty(); + let certs = rustls_native_certs::load_native_certs().expect("Certs not loadable!"); + let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect(); + roots.add_parsable_certificates(&certs); + roots +} From 15dbde0a9c77d741e58578662a8cd36987f915a5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 28 Aug 2023 17:59:24 -0700 Subject: [PATCH 017/211] Add history section to the readme. (#687) Signed-off-by: David Calavera --- lambda-events/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lambda-events/README.md b/lambda-events/README.md index 0813c63a..8446ce55 100644 --- a/lambda-events/README.md +++ b/lambda-events/README.md @@ -27,6 +27,12 @@ This crate divides all Lambda Events into features named after the service that cargo add aws_lambda_events --no-default-features --features apigw,alb ``` +## History + +The AWS Lambda Events crate was created by [Christian Legnitto](https://github.com/LegNeato). Without all his work and dedication, this project could have not been possible. + +In 2023, the AWS Lambda Event crate was moved into this repository to continue its support for all AWS customers that use Rust on AWS Lambda. + [//]: # 'badges' [crate-image]: https://img.shields.io/crates/v/aws_lambda_events.svg [crate-link]: https://crates.io/crates/aws_lambda_events From 1781f890a3e63fd73b5a00b8d62bce057a19e65a Mon Sep 17 00:00:00 2001 From: FalkWoldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Wed, 30 Aug 2023 18:21:14 +0200 Subject: [PATCH 018/211] Make Kafka header values i8 instead of u8 (#689) --- lambda-events/src/event/kafka/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 07299859..8cd92bdf 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -28,7 +28,7 @@ pub struct KafkaRecord { pub timestamp_type: Option, pub key: Option, pub value: Option, - pub headers: Vec>>, + pub headers: Vec>>, } #[cfg(test)] From e6ac88fe43edf1fcb59ad0c91487f23f6b267e88 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 4 Sep 2023 19:59:44 -0700 Subject: [PATCH 019/211] Fix streaming prelude serialization (#692) * Fix streaming prelude serialization HTTP headers can be multi-value, but the current implementation ignores this fact and only serializes the first value for each header. This changes uses http-serde to serialize the prelude correctly. Signed-off-by: David Calavera * Update MSRV Some dependencies have dropped support for 1.62 already. Signed-off-by: David Calavera * Remove unwrap Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime/Cargo.toml | 1 + lambda-runtime/src/streaming.rs | 68 ++++++++++++++------------- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 3a56e597..4e5fb34d 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 0905f289..7365bc64 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 68913c95..9657a840 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.62.0" # Current MSRV + - "1.64.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index 12dbf523..5f6f899a 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.62, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.64, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1de6f361..197265ab 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -42,3 +42,4 @@ tower = { version = "0.4", features = ["util"] } tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" +http-serde = "1.1.3" diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs index e541f3d6..5ea369ad 100644 --- a/lambda-runtime/src/streaming.rs +++ b/lambda-runtime/src/streaming.rs @@ -5,13 +5,11 @@ use crate::{ use bytes::Bytes; use futures::FutureExt; use http::header::{CONTENT_TYPE, SET_COOKIE}; -use http::{Method, Request, Response, Uri}; +use http::{HeaderMap, Method, Request, Response, StatusCode, Uri}; use hyper::body::HttpBody; use hyper::{client::connect::Connection, Body}; use lambda_runtime_api_client::{build_request, Client}; -use serde::Deserialize; -use serde_json::json; -use std::collections::HashMap; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::{ env, @@ -203,6 +201,16 @@ pub(crate) struct EventCompletionStreamingRequest<'a, B> { pub(crate) body: Response, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct MetadataPrelude { + #[serde(serialize_with = "http_serde::status_code::serialize")] + status_code: StatusCode, + #[serde(serialize_with = "http_serde::header_map::serialize")] + headers: HeaderMap, + cookies: Vec, +} + impl<'a, B> IntoRequest for EventCompletionStreamingRequest<'a, B> where B: HttpBody + Unpin + Send + 'static, @@ -216,45 +224,39 @@ where let (parts, mut body) = self.body.into_parts(); let mut builder = build_request().method(Method::POST).uri(uri); - let headers = builder.headers_mut().unwrap(); + let req_headers = builder.headers_mut().unwrap(); - headers.insert("Transfer-Encoding", "chunked".parse()?); - headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); - headers.insert( + req_headers.insert("Transfer-Encoding", "chunked".parse()?); + req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); + req_headers.insert( "Content-Type", "application/vnd.awslambda.http-integration-response".parse()?, ); - let (mut tx, rx) = Body::channel(); + let mut prelude_headers = parts.headers; + // default Content-Type + prelude_headers + .entry(CONTENT_TYPE) + .or_insert("application/octet-stream".parse()?); - tokio::spawn(async move { - let mut header_map = parts.headers; - // default Content-Type - header_map - .entry(CONTENT_TYPE) - .or_insert("application/octet-stream".parse().unwrap()); + let cookies = prelude_headers.get_all(SET_COOKIE); + let cookies = cookies + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + prelude_headers.remove(SET_COOKIE); - let cookies = header_map.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); + let metadata_prelude = serde_json::to_string(&MetadataPrelude { + status_code: parts.status, + headers: prelude_headers, + cookies, + })?; - let headers = header_map - .iter() - .filter(|(k, _)| *k != SET_COOKIE) - .map(|(k, v)| (k.as_str(), String::from_utf8_lossy(v.as_bytes()).to_string())) - .collect::>(); + trace!(?metadata_prelude); - let metadata_prelude = json!({ - "statusCode": parts.status.as_u16(), - "headers": headers, - "cookies": cookies, - }) - .to_string(); - - trace!("metadata_prelude: {}", metadata_prelude); + let (mut tx, rx) = Body::channel(); + tokio::spawn(async move { tx.send_data(metadata_prelude.into()).await.unwrap(); tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); From d1d58235a48511448c47f1314a75ed397cbf94d2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 5 Sep 2023 07:54:30 -0700 Subject: [PATCH 020/211] Release runtime and events new versions (#693) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 27b577cb..a21973b2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.11.0" +version = "0.11.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 197265ab..1137f845 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.1" +version = "0.8.2" authors = [ "David Calavera ", "Harold Sun ", From e2d51ad4e3bb2c04162049eaf4b50cbdff0fe06c Mon Sep 17 00:00:00 2001 From: Chris Leach <7308018+chris-leach@users.noreply.github.com> Date: Mon, 11 Sep 2023 19:26:28 +0100 Subject: [PATCH 021/211] Add event definitions for CloudFormation custom resources (#695) * Add event definitions for CloudFormation custom resources * Remove let else statements * Fix codebuild_time for chrono ^0.4.29 by parsing via NaiveDateTime --------- Co-authored-by: Chris Leach --- lambda-events/Cargo.toml | 2 + .../src/custom_serde/codebuild_time.rs | 17 ++- lambda-events/src/event/cloudformation/mod.rs | 143 ++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + ...mation-custom-resource-create-request.json | 14 ++ ...mation-custom-resource-delete-request.json | 15 ++ ...mation-custom-resource-update-request.json | 20 +++ lambda-events/src/lib.rs | 5 + 8 files changed, 212 insertions(+), 8 deletions(-) create mode 100644 lambda-events/src/event/cloudformation/mod.rs create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index a21973b2..e401bfff 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -42,6 +42,7 @@ default = [ "autoscaling", "chime_bot", "clientvpn", + "cloudformation", "cloudwatch_events", "cloudwatch_logs", "code_commit", @@ -81,6 +82,7 @@ appsync = [] autoscaling = ["chrono"] chime_bot = ["chrono"] clientvpn = [] +cloudformation = [] cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] code_commit = ["chrono"] diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 94d0e2f5..bd132b23 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, TimeZone, Utc}; +use chrono::{DateTime, NaiveDateTime, Utc}; use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError, Visitor}, @@ -18,7 +18,8 @@ impl<'de> Visitor<'de> for TimeVisitor { } fn visit_str(self, val: &str) -> Result { - Utc.datetime_from_str(val, CODEBUILD_TIME_FORMAT) + NaiveDateTime::parse_from_str(val, CODEBUILD_TIME_FORMAT) + .map(|naive| naive.and_utc()) .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) } } @@ -81,9 +82,9 @@ mod tests { "date": "Sep 1, 2017 4:12:29 PM" }); - let expected = Utc - .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) - .unwrap(); + let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap() + .and_utc(); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(expected, decoded.date); } @@ -99,9 +100,9 @@ mod tests { "date": "Sep 1, 2017 4:12:29 PM" }); - let expected = Utc - .datetime_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) - .unwrap(); + let expected = NaiveDateTime::parse_from_str("Sep 1, 2017 4:12:29 PM", CODEBUILD_TIME_FORMAT) + .unwrap() + .and_utc(); let decoded: Test = serde_json::from_value(data).unwrap(); assert_eq!(Some(expected), decoded.date); } diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs new file mode 100644 index 00000000..e2d51745 --- /dev/null +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -0,0 +1,143 @@ +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(tag = "RequestType")] +pub enum CloudFormationCustomResourceRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(bound = "")] + Create(CreateRequest), + #[serde(bound = "")] + Update(UpdateRequest), + #[serde(bound = "")] + Delete(DeleteRequest), +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct CreateRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct UpdateRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + pub physical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, + #[serde(bound = "")] + pub old_resource_properties: P1, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(deny_unknown_fields)] +pub struct DeleteRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(default)] + pub service_token: Option, + pub request_id: String, + #[serde(rename = "ResponseURL")] + pub response_url: String, + pub stack_id: String, + pub resource_type: String, + pub logical_resource_id: String, + pub physical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use super::CloudFormationCustomResourceRequest::*; + use super::*; + + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] + #[serde(rename_all = "PascalCase")] + #[serde(deny_unknown_fields)] + struct TestProperties { + key_1: String, + key_2: Vec, + key_3: HashMap, + } + + type TestRequest = CloudFormationCustomResourceRequest; + + #[test] + fn example_cloudformation_custom_resource_create_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-create-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Create(_) => (), + _ => panic!("expected Create request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_cloudformation_custom_resource_update_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-update-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Update(_) => (), + _ => panic!("expected Update request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_cloudformation_custom_resource_delete_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-delete-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Delete(_) => (), + _ => panic!("expected Delete request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 1aa56697..4ce71dfc 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -25,6 +25,10 @@ pub mod chime_bot; #[cfg(feature = "clientvpn")] pub mod clientvpn; +/// AWS Lambda event definitions for cloudformation. +#[cfg(feature = "cloudformation")] +pub mod cloudformation; + /// CloudWatch Events payload #[cfg(feature = "cloudwatch_events")] pub mod cloudwatch_events; diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json new file mode 100644 index 00000000..d35dd6f7 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-create-request.json @@ -0,0 +1,14 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Create", + "RequestId" : "82304eb2-bdda-469f-a33b-a3f1406d0a52", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json new file mode 100644 index 00000000..bd788c99 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-delete-request.json @@ -0,0 +1,15 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Delete", + "RequestId" : "ef70561d-d4ba-42a4-801b-33ad88dafc37", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json new file mode 100644 index 00000000..4fc4378a --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-update-request.json @@ -0,0 +1,20 @@ +{ + "ServiceToken": "arn:aws:lambda:eu-west-2:123456789012:function:custom-resource-handler", + "RequestType" : "Update", + "RequestId" : "49347ca5-c603-44e5-a34b-10cf1854a887", + "ResponseURL": "https://cr-response-bucket.s3.us-east-1.amazonaws.com/cr-response-key?sig-params=sig-values", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "new-string", + "Key2" : [ "new-list" ], + "Key3" : { "Key4" : "new-map" } + }, + "OldResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 564debd7..7402a8f4 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -20,6 +20,7 @@ pub use event::activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] pub use event::alb; + /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] pub use event::apigw; @@ -40,6 +41,10 @@ pub use event::chime_bot; #[cfg(feature = "clientvpn")] pub use event::clientvpn; +/// AWS Lambda event definitions for cloudformation +#[cfg(feature = "cloudformation")] +pub use event::cloudformation; + /// CloudWatch Events payload #[cfg(feature = "cloudwatch_events")] pub use event::cloudwatch_events; From cf72bb05c59c3ca094d169e924774f17972f4b2d Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Fri, 15 Sep 2023 10:46:17 +0800 Subject: [PATCH 022/211] Refactor Lambda response streaming. (#696) * Refactor Lambda response streaming. Remove the separate streaming.rs from lambda-runtime crate. Merge into the `run` method. Added FunctionResponse enum to capture both buffered response and streaming response. Added IntoFunctionResponse trait to convert `Serialize` response into FunctionResponse::BufferedResponse, and convert `Stream` response into FunctionResponse::StreamingResponse. Existing handler functions should continue to work. Improved error handling in response streaming. Return trailers to report errors instead of panic. * Add comments for reporting midstream errors using error trailers * Remove "pub" from internal run method --- examples/basic-streaming-response/README.md | 2 +- examples/basic-streaming-response/src/main.rs | 18 +- lambda-http/Cargo.toml | 1 + lambda-http/src/streaming.rs | 67 ++++- lambda-runtime/Cargo.toml | 2 + lambda-runtime/src/lib.rs | 29 +- lambda-runtime/src/requests.rs | 94 +++++- lambda-runtime/src/streaming.rs | 272 ------------------ lambda-runtime/src/types.rs | 89 +++++- 9 files changed, 266 insertions(+), 308 deletions(-) delete mode 100644 lambda-runtime/src/streaming.rs diff --git a/examples/basic-streaming-response/README.md b/examples/basic-streaming-response/README.md index 3b68f518..ac744a33 100644 --- a/examples/basic-streaming-response/README.md +++ b/examples/basic-streaming-response/README.md @@ -6,7 +6,7 @@ 2. Build the function with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` 4. Enable Lambda streaming response on Lambda console: change the function url's invoke mode to `RESPONSE_STREAM` -5. Verify the function works: `curl `. The results should be streamed back with 0.5 second pause between each word. +5. Verify the function works: `curl -v -N `. The results should be streamed back with 0.5 second pause between each word. ## Build for ARM 64 diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index d90ebd33..9d505206 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,9 +1,9 @@ -use hyper::{body::Body, Response}; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use hyper::body::Body; +use lambda_runtime::{service_fn, Error, LambdaEvent, StreamResponse}; use serde_json::Value; use std::{thread, time::Duration}; -async fn func(_event: LambdaEvent) -> Result, Error> { +async fn func(_event: LambdaEvent) -> Result, Error> { let messages = vec!["Hello", "world", "from", "Lambda!"]; let (mut tx, rx) = Body::channel(); @@ -15,12 +15,10 @@ async fn func(_event: LambdaEvent) -> Result, Error> { } }); - let resp = Response::builder() - .header("content-type", "text/html") - .header("CustomHeader", "outerspace") - .body(rx)?; - - Ok(resp) + Ok(StreamResponse { + metadata_prelude: Default::default(), + stream: rx, + }) } #[tokio::main] @@ -34,6 +32,6 @@ async fn main() -> Result<(), Error> { .without_time() .init(); - lambda_runtime::run_with_streaming_response(service_fn(func)).await?; + lambda_runtime::run(service_fn(func)).await?; Ok(()) } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index be111092..ea4a5fba 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -33,6 +33,7 @@ lambda_runtime = { path = "../lambda-runtime", version = "0.8" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" +tokio-stream = "0.1.2" mime = "0.3" encoding_rs = "0.8" url = "2.2" diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 9a27d915..a59cf700 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,3 +1,4 @@ +use crate::http::header::SET_COOKIE; use crate::tower::ServiceBuilder; use crate::Request; use crate::{request::LambdaRequest, RequestExt}; @@ -5,9 +6,14 @@ pub use aws_lambda_events::encodings::Body as LambdaEventBody; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; -use lambda_runtime::LambdaEvent; -pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +pub use lambda_runtime::{ + self, service_fn, tower, tower::ServiceExt, Error, FunctionResponse, LambdaEvent, MetadataPrelude, Service, + StreamResponse, +}; use std::fmt::{Debug, Display}; +use std::pin::Pin; +use std::task::{Context, Poll}; +use tokio_stream::Stream; /// Starts the Lambda Rust runtime and stream response back [Configure Lambda /// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). @@ -28,7 +34,60 @@ where let event: Request = req.payload.into(); event.with_lambda_context(req.context) }) - .service(handler); + .service(handler) + .map_response(|res| { + let (parts, body) = res.into_parts(); - lambda_runtime::run_with_streaming_response(svc).await + let mut prelude_headers = parts.headers; + + let cookies = prelude_headers.get_all(SET_COOKIE); + let cookies = cookies + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + + prelude_headers.remove(SET_COOKIE); + + let metadata_prelude = MetadataPrelude { + headers: prelude_headers, + status_code: parts.status, + cookies, + }; + + StreamResponse { + metadata_prelude, + stream: BodyStream { body }, + } + }); + + lambda_runtime::run(svc).await +} + +pub struct BodyStream { + pub(crate) body: B, +} + +impl BodyStream +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + fn project(self: Pin<&mut Self>) -> Pin<&mut B> { + unsafe { self.map_unchecked_mut(|s| &mut s.body) } + } +} + +impl Stream for BodyStream +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + type Item = Result; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let body = self.project(); + body.poll_data(cx) + } } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1137f845..9202b1c1 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -43,3 +43,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" http-serde = "1.1.3" +base64 = "0.20.0" +http-body = "0.4" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e3ffd49d..18b1066e 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,6 +7,7 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. +use bytes::Bytes; use futures::FutureExt; use hyper::{ client::{connect::Connection, HttpConnector}, @@ -20,6 +21,7 @@ use std::{ env, fmt::{self, Debug, Display}, future::Future, + marker::PhantomData, panic, }; use tokio::io::{AsyncRead, AsyncWrite}; @@ -35,11 +37,8 @@ mod simulated; /// Types available to a Lambda function. mod types; -mod streaming; -pub use streaming::run_with_streaming_response; - use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; -pub use types::{Context, LambdaEvent}; +pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::Error; @@ -97,17 +96,21 @@ where C::Error: Into>, C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, { - async fn run( + async fn run( &self, incoming: impl Stream, Error>> + Send, mut handler: F, ) -> Result<(), Error> where F: Service>, - F::Future: Future>, + F::Future: Future>, F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, + R: IntoFunctionResponse, B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { let client = &self.client; tokio::pin!(incoming); @@ -177,6 +180,8 @@ where EventCompletionRequest { request_id, body: response, + _unused_b: PhantomData, + _unused_s: PhantomData, } .into_req() } @@ -243,13 +248,17 @@ where /// Ok(event.payload) /// } /// ``` -pub async fn run(handler: F) -> Result<(), Error> +pub async fn run(handler: F) -> Result<(), Error> where F: Service>, - F::Future: Future>, + F::Future: Future>, F::Error: fmt::Debug + fmt::Display, A: for<'de> Deserialize<'de>, + R: IntoFunctionResponse, B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { trace!("Loading config from env"); let config = Config::from_env()?; @@ -293,7 +302,7 @@ mod endpoint_tests { use lambda_runtime_api_client::Client; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env}; + use std::{convert::TryFrom, env, marker::PhantomData}; use tokio::{ io::{self, AsyncRead, AsyncWrite}, select, @@ -430,6 +439,8 @@ mod endpoint_tests { let req = EventCompletionRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", body: "done", + _unused_b: PhantomData::<&str>, + _unused_s: PhantomData::, }; let req = req.into_req()?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 26257d20..8e72fc2d 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,9 +1,15 @@ -use crate::{types::Diagnostic, Error}; +use crate::types::ToStreamErrorTrailer; +use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; +use bytes::Bytes; +use http::header::CONTENT_TYPE; use http::{Method, Request, Response, Uri}; use hyper::Body; use lambda_runtime_api_client::build_request; use serde::Serialize; +use std::fmt::Debug; +use std::marker::PhantomData; use std::str::FromStr; +use tokio_stream::{Stream, StreamExt}; pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; @@ -65,23 +71,87 @@ fn test_next_event_request() { } // /runtime/invocation/{AwsRequestId}/response -pub(crate) struct EventCompletionRequest<'a, T> { +pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> +where + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ pub(crate) request_id: &'a str, - pub(crate) body: T, + pub(crate) body: R, + pub(crate) _unused_b: PhantomData, + pub(crate) _unused_s: PhantomData, } -impl<'a, T> IntoRequest for EventCompletionRequest<'a, T> +impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> where - T: for<'serialize> Serialize, + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, { fn into_req(self) -> Result, Error> { - let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); - let uri = Uri::from_str(&uri)?; - let body = serde_json::to_vec(&self.body)?; - let body = Body::from(body); + match self.body.into_response() { + FunctionResponse::BufferedResponse(body) => { + let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); + let uri = Uri::from_str(&uri)?; - let req = build_request().method(Method::POST).uri(uri).body(body)?; - Ok(req) + let body = serde_json::to_vec(&body)?; + let body = Body::from(body); + + let req = build_request().method(Method::POST).uri(uri).body(body)?; + Ok(req) + } + FunctionResponse::StreamingResponse(mut response) => { + let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); + let uri = Uri::from_str(&uri)?; + + let mut builder = build_request().method(Method::POST).uri(uri); + let req_headers = builder.headers_mut().unwrap(); + + req_headers.insert("Transfer-Encoding", "chunked".parse()?); + req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); + // Report midstream errors using error trailers. + // See the details in Lambda Developer Doc: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html#runtimes-custom-response-streaming + req_headers.append("Trailer", "Lambda-Runtime-Function-Error-Type".parse()?); + req_headers.append("Trailer", "Lambda-Runtime-Function-Error-Body".parse()?); + req_headers.insert( + "Content-Type", + "application/vnd.awslambda.http-integration-response".parse()?, + ); + + // default Content-Type + let preloud_headers = &mut response.metadata_prelude.headers; + preloud_headers + .entry(CONTENT_TYPE) + .or_insert("application/octet-stream".parse()?); + + let metadata_prelude = serde_json::to_string(&response.metadata_prelude)?; + + tracing::trace!(?metadata_prelude); + + let (mut tx, rx) = Body::channel(); + + tokio::spawn(async move { + tx.send_data(metadata_prelude.into()).await.unwrap(); + tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); + + while let Some(chunk) = response.stream.next().await { + let chunk = match chunk { + Ok(chunk) => chunk.into(), + Err(err) => err.into().to_tailer().into(), + }; + tx.send_data(chunk).await.unwrap(); + } + }); + + let req = builder.body(rx)?; + Ok(req) + } + } } } @@ -90,6 +160,8 @@ fn test_event_completion_request() { let req = EventCompletionRequest { request_id: "id", body: "hello, world!", + _unused_b: PhantomData::<&str>, + _unused_s: PhantomData::, }; let req = req.into_req().unwrap(); let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/response"); diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs deleted file mode 100644 index 5ea369ad..00000000 --- a/lambda-runtime/src/streaming.rs +++ /dev/null @@ -1,272 +0,0 @@ -use crate::{ - build_event_error_request, deserializer, incoming, type_name_of_val, Config, Context, Error, EventErrorRequest, - IntoRequest, LambdaEvent, Runtime, -}; -use bytes::Bytes; -use futures::FutureExt; -use http::header::{CONTENT_TYPE, SET_COOKIE}; -use http::{HeaderMap, Method, Request, Response, StatusCode, Uri}; -use hyper::body::HttpBody; -use hyper::{client::connect::Connection, Body}; -use lambda_runtime_api_client::{build_request, Client}; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; -use std::{ - env, - fmt::{self, Debug, Display}, - future::Future, - panic, -}; -use tokio::io::{AsyncRead, AsyncWrite}; -use tokio_stream::{Stream, StreamExt}; -use tower::{Service, ServiceExt}; -use tracing::{error, trace, Instrument}; - -/// Starts the Lambda Rust runtime and stream response back [Configure Lambda -/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). -/// -/// # Example -/// ```no_run -/// use hyper::{body::Body, Response}; -/// use lambda_runtime::{service_fn, Error, LambdaEvent}; -/// use std::{thread, time::Duration}; -/// use serde_json::Value; -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Error> { -/// lambda_runtime::run_with_streaming_response(service_fn(func)).await?; -/// Ok(()) -/// } -/// async fn func(_event: LambdaEvent) -> Result, Error> { -/// let messages = vec!["Hello ", "world ", "from ", "Lambda!"]; -/// -/// let (mut tx, rx) = Body::channel(); -/// -/// tokio::spawn(async move { -/// for message in messages.iter() { -/// tx.send_data((*message).into()).await.unwrap(); -/// thread::sleep(Duration::from_millis(500)); -/// } -/// }); -/// -/// let resp = Response::builder() -/// .header("content-type", "text/plain") -/// .header("CustomHeader", "outerspace") -/// .body(rx)?; -/// -/// Ok(resp) -/// } -/// ``` -pub async fn run_with_streaming_response(handler: F) -> Result<(), Error> -where - F: Service>, - F::Future: Future, F::Error>>, - F::Error: Debug + Display, - A: for<'de> Deserialize<'de>, - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - trace!("Loading config from env"); - let config = Config::from_env()?; - let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client, config }; - - let client = &runtime.client; - let incoming = incoming(client); - runtime.run_with_streaming_response(incoming, handler).await -} - -impl Runtime -where - C: Service + Clone + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send, - C::Error: Into>, - C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, -{ - async fn run_with_streaming_response( - &self, - incoming: impl Stream, Error>> + Send, - mut handler: F, - ) -> Result<(), Error> - where - F: Service>, - F::Future: Future, F::Error>>, - F::Error: fmt::Debug + fmt::Display, - A: for<'de> Deserialize<'de>, - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, - { - let client = &self.client; - tokio::pin!(incoming); - while let Some(next_event_response) = incoming.next().await { - trace!("New event arrived (run loop)"); - let event = next_event_response?; - let (parts, body) = event.into_parts(); - - #[cfg(debug_assertions)] - if parts.status == http::StatusCode::NO_CONTENT { - // Ignore the event if the status code is 204. - // This is a way to keep the runtime alive when - // there are no events pending to be processed. - continue; - } - - let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(&self.config); - let request_id = &ctx.request_id.clone(); - - let request_span = match &ctx.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = request_id) - } - }; - - // Group the handling in one future and instrument it with the span - async { - let body = hyper::body::to_bytes(body).await?; - trace!("incoming request payload - {}", std::str::from_utf8(&body)?); - - #[cfg(debug_assertions)] - if parts.status.is_server_error() { - error!("Lambda Runtime server returned an unexpected error"); - return Err(parts.status.to_string().into()); - } - - let lambda_event = match deserializer::deserialize(&body, ctx) { - Ok(lambda_event) => lambda_event, - Err(err) => { - let req = build_event_error_request(request_id, err)?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); - return Ok(()); - } - }; - - let req = match handler.ready().await { - Ok(handler) => { - // Catches panics outside of a `Future` - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); - - let task = match task { - // Catches panics inside of the `Future` - Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, - Err(err) => Err(err), - }; - - match task { - Ok(response) => match response { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionStreamingRequest { - request_id, - body: response, - } - .into_req() - } - Err(err) => build_event_error_request(request_id, err), - }, - Err(err) => { - error!("{:?}", err); - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {msg}") - } else { - "Lambda panicked".to_string() - }; - EventErrorRequest::new(request_id, error_type, &msg).into_req() - } - } - } - Err(err) => build_event_error_request(request_id, err), - }?; - - client.call(req).await.expect("Unable to send response to Runtime APIs"); - Ok::<(), Error>(()) - } - .instrument(request_span) - .await?; - } - Ok(()) - } -} - -pub(crate) struct EventCompletionStreamingRequest<'a, B> { - pub(crate) request_id: &'a str, - pub(crate) body: Response, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -struct MetadataPrelude { - #[serde(serialize_with = "http_serde::status_code::serialize")] - status_code: StatusCode, - #[serde(serialize_with = "http_serde::header_map::serialize")] - headers: HeaderMap, - cookies: Vec, -} - -impl<'a, B> IntoRequest for EventCompletionStreamingRequest<'a, B> -where - B: HttpBody + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - fn into_req(self) -> Result, Error> { - let uri = format!("/2018-06-01/runtime/invocation/{}/response", self.request_id); - let uri = Uri::from_str(&uri)?; - - let (parts, mut body) = self.body.into_parts(); - - let mut builder = build_request().method(Method::POST).uri(uri); - let req_headers = builder.headers_mut().unwrap(); - - req_headers.insert("Transfer-Encoding", "chunked".parse()?); - req_headers.insert("Lambda-Runtime-Function-Response-Mode", "streaming".parse()?); - req_headers.insert( - "Content-Type", - "application/vnd.awslambda.http-integration-response".parse()?, - ); - - let mut prelude_headers = parts.headers; - // default Content-Type - prelude_headers - .entry(CONTENT_TYPE) - .or_insert("application/octet-stream".parse()?); - - let cookies = prelude_headers.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); - prelude_headers.remove(SET_COOKIE); - - let metadata_prelude = serde_json::to_string(&MetadataPrelude { - status_code: parts.status, - headers: prelude_headers, - cookies, - })?; - - trace!(?metadata_prelude); - - let (mut tx, rx) = Body::channel(); - - tokio::spawn(async move { - tx.send_data(metadata_prelude.into()).await.unwrap(); - tx.send_data("\u{0}".repeat(8).into()).await.unwrap(); - - while let Some(chunk) = body.data().await { - let chunk = chunk.unwrap(); - tx.send_data(chunk.into()).await.unwrap(); - } - }); - - let req = builder.body(rx)?; - Ok(req) - } -} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 87d6ded5..27a4a9ae 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,11 +1,14 @@ use crate::{Config, Error}; -use http::{HeaderMap, HeaderValue}; +use bytes::Bytes; +use http::{HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, convert::TryFrom, + fmt::Debug, time::{Duration, SystemTime}, }; +use tokio_stream::Stream; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -182,6 +185,90 @@ impl LambdaEvent { } } +/// Metadata prelude for a stream response. +#[derive(Debug, Default, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MetadataPrelude { + #[serde(with = "http_serde::status_code")] + /// The HTTP status code. + pub status_code: StatusCode, + #[serde(with = "http_serde::header_map")] + /// The HTTP headers. + pub headers: HeaderMap, + /// The HTTP cookies. + pub cookies: Vec, +} + +pub trait ToStreamErrorTrailer { + /// Convert the hyper error into a stream error trailer. + fn to_tailer(&self) -> String; +} + +impl ToStreamErrorTrailer for Error { + fn to_tailer(&self) -> String { + format!( + "Lambda-Runtime-Function-Error-Type: Runtime.StreamError\r\nLambda-Runtime-Function-Error-Body: {}\r\n", + base64::encode(self.to_string()) + ) + } +} + +/// A streaming response that contains the metadata prelude and the stream of bytes that will be +/// sent to the client. +#[derive(Debug)] +pub struct StreamResponse { + /// The metadata prelude. + pub metadata_prelude: MetadataPrelude, + /// The stream of bytes that will be sent to the client. + pub stream: S, +} + +/// An enum representing the response of a function that can return either a buffered +/// response of type `B` or a streaming response of type `S`. +pub enum FunctionResponse { + /// A buffered response containing the entire payload of the response. This is useful + /// for responses that can be processed quickly and have a relatively small payload size(<= 6MB). + BufferedResponse(B), + /// A streaming response that delivers the payload incrementally. This is useful for + /// large payloads(> 6MB) or responses that take a long time to generate. The client can start + /// processing the response as soon as the first chunk is available, without waiting + /// for the entire payload to be generated. + StreamingResponse(StreamResponse), +} + +/// a trait that can be implemented for any type that can be converted into a FunctionResponse. +/// This allows us to use the `into` method to convert a type into a FunctionResponse. +pub trait IntoFunctionResponse { + /// Convert the type into a FunctionResponse. + fn into_response(self) -> FunctionResponse; +} + +impl IntoFunctionResponse for FunctionResponse { + fn into_response(self) -> FunctionResponse { + self + } +} + +impl IntoFunctionResponse for B +where + B: Serialize, +{ + fn into_response(self) -> FunctionResponse { + FunctionResponse::BufferedResponse(self) + } +} + +impl IntoFunctionResponse<(), S> for StreamResponse +where + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + fn into_response(self) -> FunctionResponse<(), S> { + FunctionResponse::StreamingResponse(self) + } +} + #[cfg(test)] mod test { use super::*; From 722e33f2feb0f0f24f8f276b59d4ef875ccbc0e4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Sep 2023 10:19:26 -0700 Subject: [PATCH 023/211] Use compile_error if no http features are enabled (#698) - Provide help so people know that they have to enable a feature. - Put imports in place so conditional compilation doesn't try to compile unnecesary code. Signed-off-by: David Calavera --- lambda-http/src/deserializer.rs | 23 ++++++++++------------- lambda-http/src/request.rs | 31 ++++++++++++++++++++++--------- lambda-http/src/response.rs | 7 +++++++ 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 1771ea7b..a77f68a5 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -1,8 +1,4 @@ use crate::request::LambdaRequest; -use aws_lambda_events::{ - alb::AlbTargetGroupRequest, - apigw::{ApiGatewayProxyRequest, ApiGatewayV2httpRequest, ApiGatewayWebsocketProxyRequest}, -}; use serde::{de::Error, Deserialize}; const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; @@ -17,28 +13,29 @@ impl<'de> Deserialize<'de> for LambdaRequest { Err(err) => return Err(err), }; #[cfg(feature = "apigw_rest")] - if let Ok(res) = - ApiGatewayProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) - { + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayProxyRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { return Ok(LambdaRequest::ApiGatewayV1(res)); } #[cfg(feature = "apigw_http")] - if let Ok(res) = ApiGatewayV2httpRequest::deserialize( + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayV2httpRequest::deserialize( serde::__private::de::ContentRefDeserializer::::new(&content), ) { return Ok(LambdaRequest::ApiGatewayV2(res)); } #[cfg(feature = "alb")] if let Ok(res) = - AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::::new(&content)) + aws_lambda_events::alb::AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::< + D::Error, + >::new(&content)) { return Ok(LambdaRequest::Alb(res)); } #[cfg(feature = "apigw_websockets")] - if let Ok(res) = ApiGatewayWebsocketProxyRequest::deserialize(serde::__private::de::ContentRefDeserializer::< - D::Error, - >::new(&content)) - { + if let Ok(res) = aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest::deserialize( + serde::__private::de::ContentRefDeserializer::::new(&content), + ) { return Ok(LambdaRequest::WebSocket(res)); } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index bdb755ed..ad86e5a5 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -8,6 +8,12 @@ //! [`RequestExt`]: crate::RequestExt #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] use crate::ext::extensions::{PathParameters, StageVariables}; +#[cfg(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" +))] use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; @@ -26,7 +32,7 @@ use serde_json::error::Error as JsonError; use std::future::Future; use std::pin::Pin; -use std::{env, io::Read, mem}; +use std::{env, io::Read}; use url::Url; /// Internal representation of an Lambda http event from @@ -61,6 +67,13 @@ impl LambdaRequest { LambdaRequest::Alb { .. } => RequestOrigin::Alb, #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, + #[cfg(not(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" + )))] + _ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, or `apigw_websockets` must be enabled for the `lambda-http` crate."), } } } @@ -141,8 +154,8 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request http::Request { .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method); req } @@ -255,8 +268,8 @@ fn into_alb_request(alb: AlbTargetGroupRequest) -> http::Request { .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method); req } @@ -319,8 +332,8 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< .expect("failed to build request"); // no builder method that sets headers in batch - let _ = mem::replace(req.headers_mut(), headers); - let _ = mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET)); + let _ = std::mem::replace(req.headers_mut(), headers); + let _ = std::mem::replace(req.method_mut(), http_method.unwrap_or(http::Method::GET)); req } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 1a2ede5c..a51d1b2d 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -114,6 +114,13 @@ impl LambdaResponse { headers: headers.clone(), multi_value_headers: headers, }), + #[cfg(not(any( + feature = "apigw_rest", + feature = "apigw_http", + feature = "alb", + feature = "apigw_websockets" + )))] + _ => compile_error!("Either feature `apigw_rest`, `apigw_http`, `alb`, or `apigw_websockets` must be enabled for the `lambda-http` crate."), } } } From d1687e174ec2037ae786cc6407d1aca5dbe271f5 Mon Sep 17 00:00:00 2001 From: Blake Jakopovic Date: Tue, 19 Sep 2023 19:59:18 +0200 Subject: [PATCH 024/211] Update README.md (#699) Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f6f899a..cd7bbcfc 100644 --- a/README.md +++ b/README.md @@ -278,7 +278,7 @@ $ docker run --rm \ rustserverless/lambda-rust ``` -With your application build and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: +With your application built and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: ```bash # start a docker container replicating the "provided" lambda runtime From 1cbe34b7cefbddd819013830a24a012028ea91f1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 18 Oct 2023 09:29:33 -0700 Subject: [PATCH 025/211] Fix time serialization issues (#707) - Update Chrono to fix compilation issues. - Update leap second tests. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 7 +++++-- lambda-events/src/encodings/time.rs | 6 +++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index e401bfff..73ab06d8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -25,12 +25,15 @@ serde_with = { version = "^3", features = ["json"], optional = true } serde_json = "^1" serde_dynamo = { version = "^4.1", optional = true } bytes = { version = "1", features = ["serde"], optional = true } -chrono = { version = "0.4.23", default-features = false, features = [ +chrono = { version = "0.4.31", default-features = false, features = [ "clock", "serde", "std", ], optional = true } -query_map = { version = "^0.7", features = ["serde", "url-query"], optional = true } +query_map = { version = "^0.7", features = [ + "serde", + "url-query", +], optional = true } flate2 = { version = "1.0.24", optional = true } [features] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 390927ca..a550b7b0 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -279,12 +279,12 @@ mod test { let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":"427683600.002"}"#)); - // Make sure milliseconds are included. + // Make sure leap seconds are included. let instance = Test { - v: Utc.ymd(1983, 7, 22).and_hms_nano(1, 0, 0, 1_234_000_000), + v: Utc.ymd(1983, 7, 22).and_hms_nano(23, 59, 59, 1_999_999_999), }; let encoded = serde_json::to_string(&instance).unwrap(); - assert_eq!(encoded, String::from(r#"{"v":"427683601.234"}"#)); + assert_eq!(encoded, String::from(r#"{"v":"427766400.999"}"#)); } #[test] From bcd3f971016dcbe4fbbe515ec1506c92feda2799 Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 19 Oct 2023 10:30:39 -0300 Subject: [PATCH 026/211] Eventbridge Event Processor (#704) * Eventbridge Event Processor * cfg feature fix * feature comment * Removed whitespace * makefile fix --------- Co-authored-by: nich.morgan Co-authored-by: erso --- Makefile | 1 + lambda-events/Cargo.toml | 2 + lambda-events/src/event/eventbridge/mod.rs | 87 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-eventbridge-event-obj.json | 13 +++ .../fixtures/example-eventbridge-event.json | 13 +++ lambda-events/src/lib.rs | 4 + 7 files changed, 124 insertions(+) create mode 100644 lambda-events/src/event/eventbridge/mod.rs create mode 100644 lambda-events/src/fixtures/example-eventbridge-event-obj.json create mode 100644 lambda-events/src/fixtures/example-eventbridge-event.json diff --git a/Makefile b/Makefile index 544d08b7..58eb2a9c 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs cargo test --package aws_lambda_events --no-default-features --features streams + cargo test --package aws_lambda_events --no-default-features --features eventbridge fmt: cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 73ab06d8..b7a00b29 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -76,6 +76,7 @@ default = [ "sns", "sqs", "streams", + "eventbridge", ] activemq = [] @@ -117,3 +118,4 @@ ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] +eventbridge = ["chrono", "serde_with"] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs new file mode 100644 index 00000000..7809f1e2 --- /dev/null +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -0,0 +1,87 @@ +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use serde_with::serde_as; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct EventBridgeEvent { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + pub detail_type: String, + pub source: String, + #[serde(default)] + pub account: Option, + #[serde(default)] + pub time: Option>, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub resources: Option>, + #[serde(default)] + pub detail: Option, +} + +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "kebab-case")] +pub struct EventBridgeEventObj { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + pub detail_type: String, + pub source: String, + #[serde(default)] + pub account: Option, + #[serde(default)] + pub time: Option>, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub resources: Option>, + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub detail: T, +} + +#[cfg(test)] +#[cfg(feature = "eventbridge")] +mod test { + use super::*; + + use serde_json; + + #[test] + fn example_eventbridge_obj_event() { + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustomStruct { + a: String, + b: String, + } + + let data = include_bytes!("../../fixtures/example-eventbridge-event-obj.json"); + let parsed: EventBridgeEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.detail.a, "123"); + assert_eq!(parsed.detail.b, "456"); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_eventbridge_event() { + let data = include_bytes!("../../fixtures/example-eventbridge-event.json"); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(parsed.detail, Some(String::from("String Message"))); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 4ce71dfc..46dc760c 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -140,3 +140,7 @@ pub mod sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] pub mod streams; + +/// AWS Lambda event definitions for EventBridge. +#[cfg(feature = "eventbridge")] +pub mod eventbridge; diff --git a/lambda-events/src/fixtures/example-eventbridge-event-obj.json b/lambda-events/src/fixtures/example-eventbridge-event-obj.json new file mode 100644 index 00000000..97c5e0ae --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-event-obj.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "111122223333", + "time": "2017-12-22T18:43:48Z", + "region": "us-west-1", + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": "{\"a\":\"123\",\"b\":\"456\"}" +} diff --git a/lambda-events/src/fixtures/example-eventbridge-event.json b/lambda-events/src/fixtures/example-eventbridge-event.json new file mode 100644 index 00000000..793ca8dc --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-event.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "detail-type": "EC2 Instance State-change Notification", + "source": "aws.ec2", + "account": "111122223333", + "time": "2017-12-22T18:43:48Z", + "region": "us-west-1", + "resources": [ + "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + ], + "detail": "String Message" +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 7402a8f4..5fe81cfc 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -164,3 +164,7 @@ pub use event::sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] pub use event::streams; + +/// AWS Lambda event definitions for EventBridge. +#[cfg(feature = "eventbridge")] +pub use event::eventbridge; From 2675fa9e305536b14756bf7290db95f21f5cc992 Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:05:51 -0300 Subject: [PATCH 027/211] DocumentDB support (#706) * insert event draft * abstract change event * Added documentdb delete event * Added support to change event drop * Added support to dropDatabase Event * - MongoDB v6.0 Change Event Fields removed - ChangeEvent enum tagged - AnyDocument common type created * replace event support * added support to invalidate event in documentdb * Adding DocumentDB Rename event. * run cargo fmt * Excluding 'to' parameter * Add DocumentDB Update event * fixed 'to' parameter and run cargo fmt * Refactoring 'Rename' event declaration as a single type not a commum type * InsertNs renamed to DatabaseCollection for code reuse * unused field removed * cfg fix * fix lines * fmt and makefile fixed * makefile reord --------- Co-authored-by: nich.morgan Co-authored-by: erso Co-authored-by: Luca Barcelos Co-authored-by: Vinicius Brisotti Co-authored-by: Pedro Rabello Sato Co-authored-by: darwish --- Makefile | 3 +- lambda-events/Cargo.toml | 2 + .../event/documentdb/events/commom_types.rs | 44 +++++++++ .../event/documentdb/events/delete_event.rs | 20 ++++ .../documentdb/events/drop_database_event.rs | 17 ++++ .../src/event/documentdb/events/drop_event.rs | 17 ++++ .../event/documentdb/events/insert_event.rs | 21 ++++ .../documentdb/events/invalidate_event.rs | 13 +++ .../src/event/documentdb/events/mod.rs | 9 ++ .../event/documentdb/events/rename_event.rs | 21 ++++ .../event/documentdb/events/replace_event.rs | 20 ++++ .../event/documentdb/events/update_event.rs | 19 ++++ lambda-events/src/event/documentdb/mod.rs | 96 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-documentdb-delete-event.json | 30 ++++++ ...xample-documentdb-drop-database-event.json | 24 +++++ .../example-documentdb-drop-event.json | 30 ++++++ .../example-documentdb-insert-event.json | 29 ++++++ .../example-documentdb-invalidate-event.json | 20 ++++ .../example-documentdb-rename-event.json | 33 +++++++ .../example-documentdb-replace-event.json | 29 ++++++ .../example-documentdb-update-event.json | 29 ++++++ lambda-events/src/lib.rs | 4 + 23 files changed, 533 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/event/documentdb/events/commom_types.rs create mode 100644 lambda-events/src/event/documentdb/events/delete_event.rs create mode 100644 lambda-events/src/event/documentdb/events/drop_database_event.rs create mode 100644 lambda-events/src/event/documentdb/events/drop_event.rs create mode 100644 lambda-events/src/event/documentdb/events/insert_event.rs create mode 100644 lambda-events/src/event/documentdb/events/invalidate_event.rs create mode 100644 lambda-events/src/event/documentdb/events/mod.rs create mode 100644 lambda-events/src/event/documentdb/events/rename_event.rs create mode 100644 lambda-events/src/event/documentdb/events/replace_event.rs create mode 100644 lambda-events/src/event/documentdb/events/update_event.rs create mode 100644 lambda-events/src/event/documentdb/mod.rs create mode 100644 lambda-events/src/fixtures/example-documentdb-delete-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-drop-database-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-drop-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-insert-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-invalidate-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-rename-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-replace-event.json create mode 100644 lambda-events/src/fixtures/example-documentdb-update-event.json diff --git a/Makefile b/Makefile index 58eb2a9c..76e57e94 100644 --- a/Makefile +++ b/Makefile @@ -81,8 +81,10 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features cognito cargo test --package aws_lambda_events --no-default-features --features config cargo test --package aws_lambda_events --no-default-features --features connect + cargo test --package aws_lambda_events --no-default-features --features documentdb cargo test --package aws_lambda_events --no-default-features --features dynamodb cargo test --package aws_lambda_events --no-default-features --features ecr_scan + cargo test --package aws_lambda_events --no-default-features --features eventbridge cargo test --package aws_lambda_events --no-default-features --features firehose cargo test --package aws_lambda_events --no-default-features --features iam cargo test --package aws_lambda_events --no-default-features --features iot @@ -101,7 +103,6 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs cargo test --package aws_lambda_events --no-default-features --features streams - cargo test --package aws_lambda_events --no-default-features --features eventbridge fmt: cargo +nightly fmt --all \ No newline at end of file diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b7a00b29..c58ec475 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -76,6 +76,7 @@ default = [ "sns", "sqs", "streams", + "documentdb", "eventbridge", ] @@ -118,4 +119,5 @@ ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] streams = [] +documentdb = [] eventbridge = ["chrono", "serde_with"] diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs new file mode 100644 index 00000000..5d1bdc19 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -0,0 +1,44 @@ +use std::collections::HashMap; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +pub type AnyDocument = HashMap; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DatabaseCollection { + db: String, + #[serde(default)] + coll: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentId { + #[serde(rename = "_data")] + pub data: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentKeyIdOid { + #[serde(rename = "$oid")] + pub oid: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentKeyId { + #[serde(rename = "_id")] + pub id: DocumentKeyIdOid, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct InnerTimestamp { + t: usize, + i: usize, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct Timestamp { + #[serde(rename = "$timestamp")] + pub timestamp: InnerTimestamp, +} diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs new file mode 100644 index 00000000..7761d62f --- /dev/null +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDeleteEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs new file mode 100644 index 00000000..c51e345c --- /dev/null +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -0,0 +1,17 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDropDatabaseEvent { + #[serde(rename = "_id")] + id: DocumentId, + cluster_time: Timestamp, + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs new file mode 100644 index 00000000..866ce143 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -0,0 +1,17 @@ +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeDropEvent { + #[serde(rename = "_id")] + id: DocumentId, + cluster_time: Timestamp, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs new file mode 100644 index 00000000..09ab66b2 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] + +pub struct ChangeInsertEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + //operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs new file mode 100644 index 00000000..47469ff9 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -0,0 +1,13 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeInvalidateEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + // operation_type: String, +} diff --git a/lambda-events/src/event/documentdb/events/mod.rs b/lambda-events/src/event/documentdb/events/mod.rs new file mode 100644 index 00000000..c1c41b98 --- /dev/null +++ b/lambda-events/src/event/documentdb/events/mod.rs @@ -0,0 +1,9 @@ +pub mod commom_types; +pub mod delete_event; +pub mod drop_database_event; +pub mod drop_event; +pub mod insert_event; +pub mod invalidate_event; +pub mod rename_event; +pub mod replace_event; +pub mod update_event; diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs new file mode 100644 index 00000000..8bc250fb --- /dev/null +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeRenameEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + //operation_type: String, + #[serde(default)] + txn_number: Option, + to: DatabaseCollection, +} diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs new file mode 100644 index 00000000..4a0e58ad --- /dev/null +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -0,0 +1,20 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeReplaceEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(default)] + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs new file mode 100644 index 00000000..8698485a --- /dev/null +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -0,0 +1,19 @@ +use serde::{Deserialize, Serialize}; + +use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChangeUpdateEvent { + #[serde(rename = "_id")] + id: DocumentId, + #[serde(default)] + cluster_time: Option, + document_key: DocumentKeyId, + #[serde(rename = "lsid")] + ls_id: Option, + ns: DatabaseCollection, + // operation_type: String, + #[serde(default)] + txn_number: Option, +} diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs new file mode 100644 index 00000000..67f7c9ad --- /dev/null +++ b/lambda-events/src/event/documentdb/mod.rs @@ -0,0 +1,96 @@ +pub mod events; + +use self::events::{ + delete_event::ChangeDeleteEvent, drop_database_event::ChangeDropDatabaseEvent, drop_event::ChangeDropEvent, + insert_event::ChangeInsertEvent, invalidate_event::ChangeInvalidateEvent, rename_event::ChangeRenameEvent, + replace_event::ChangeReplaceEvent, update_event::ChangeUpdateEvent, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(tag = "operationType", rename_all = "camelCase")] +pub enum ChangeEvent { + Insert(ChangeInsertEvent), + Delete(ChangeDeleteEvent), + Drop(ChangeDropEvent), + DropDatabase(ChangeDropDatabaseEvent), + Invalidate(ChangeInvalidateEvent), + Replace(ChangeReplaceEvent), + Update(ChangeUpdateEvent), + Rename(ChangeRenameEvent), +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +pub struct DocumentDbInnerEvent { + pub event: ChangeEvent, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DocumentDbEvent { + #[serde(default)] + pub event_source_arn: Option, + pub events: Vec, + #[serde(default)] + pub event_source: Option, +} + +#[cfg(test)] +#[cfg(feature = "documentdb")] +mod test { + use super::*; + + pub type Event = DocumentDbEvent; + + fn test_example(data: &[u8]) { + let parsed: Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: Event = serde_json::from_slice(output.as_bytes()).unwrap(); + + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_documentdb_insert_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-insert-event.json")); + } + + #[test] + fn example_documentdb_delete_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-delete-event.json")); + } + + #[test] + fn example_documentdb_drop_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-drop-event.json")); + } + + #[test] + fn example_documentdb_replace_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-replace-event.json")); + } + + #[test] + fn example_documentdb_update_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-update-event.json")); + } + + #[test] + fn example_documentdb_rename_event() { + test_example(include_bytes!("../../fixtures/example-documentdb-rename-event.json")); + } + + #[test] + fn example_documentdb_invalidate_event() { + test_example(include_bytes!( + "../../fixtures/example-documentdb-invalidate-event.json" + )); + } + + #[test] + fn example_documentdb_drop_database_event() { + test_example(include_bytes!( + "../../fixtures/example-documentdb-drop-database-event.json" + )); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 46dc760c..5ee57911 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -141,6 +141,10 @@ pub mod sqs; #[cfg(feature = "streams")] pub mod streams; +// AWS Lambda event definitions for DocumentDB +#[cfg(feature = "documentdb")] +pub mod documentdb; + /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] pub mod eventbridge; diff --git a/lambda-events/src/fixtures/example-documentdb-delete-event.json b/lambda-events/src/fixtures/example-documentdb-delete-event.json new file mode 100644 index 00000000..fd9259da --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-delete-event.json @@ -0,0 +1,30 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "delete" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-drop-database-event.json b/lambda-events/src/fixtures/example-documentdb-drop-database-event.json new file mode 100644 index 00000000..77a1cb93 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-drop-database-event.json @@ -0,0 +1,24 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "test_database" + }, + "operationType": "dropDatabase" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-drop-event.json b/lambda-events/src/fixtures/example-documentdb-drop-event.json new file mode 100644 index 00000000..89d8cc8f --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-drop-event.json @@ -0,0 +1,30 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "drop" + } + } + ], + "eventSource": "aws:docdb" + } + \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-insert-event.json b/lambda-events/src/fixtures/example-documentdb-insert-event.json new file mode 100644 index 00000000..cd03e374 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-insert-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "insert" + } + } + ], + "eventSource": "aws:docdb" +} diff --git a/lambda-events/src/fixtures/example-documentdb-invalidate-event.json b/lambda-events/src/fixtures/example-documentdb-invalidate-event.json new file mode 100644 index 00000000..59f5af65 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-invalidate-event.json @@ -0,0 +1,20 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "operationType": "invalidate" + } + } + ], + "eventSource": "aws:docdb" + } \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-rename-event.json b/lambda-events/src/fixtures/example-documentdb-rename-event.json new file mode 100644 index 00000000..65416470 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-rename-event.json @@ -0,0 +1,33 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "to": { + "db": "test_database_new", + "coll": "test_collection_new" + }, + "operationType": "rename" + } + } + ], + "eventSource": "aws:docdb" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-documentdb-replace-event.json b/lambda-events/src/fixtures/example-documentdb-replace-event.json new file mode 100644 index 00000000..1c7fe559 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-replace-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "operationType": "replace", + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "engineering", + "coll": "users" + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + } + } + } + ], + "eventSource": "aws:docdb" +} diff --git a/lambda-events/src/fixtures/example-documentdb-update-event.json b/lambda-events/src/fixtures/example-documentdb-update-event.json new file mode 100644 index 00000000..dbb19159 --- /dev/null +++ b/lambda-events/src/fixtures/example-documentdb-update-event.json @@ -0,0 +1,29 @@ +{ + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "documentKey": { + "_id": { + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "operationType": "update" + } + } + ], + "eventSource": "aws:docdb" + } \ No newline at end of file diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 5fe81cfc..aa0d5495 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -165,6 +165,10 @@ pub use event::sqs; #[cfg(feature = "streams")] pub use event::streams; +/// AWS Lambda event definitions for documentdb. +#[cfg(feature = "documentdb")] +pub use event::documentdb; + /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] pub use event::eventbridge; From b9d64e893122262e3abf0b2126b9d8dacfb265e6 Mon Sep 17 00:00:00 2001 From: Seb Maz Date: Mon, 23 Oct 2023 17:46:28 +0400 Subject: [PATCH 028/211] Updated all crates (#709) removed the deprecated mod "Attribute_value" added serde_dynamo "to_attribute_value" updated the code to match the new changes #708 --- examples/http-dynamodb/Cargo.toml | 17 +++++++++-------- examples/http-dynamodb/src/main.rs | 19 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index c3f6d8be..be95f867 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -11,15 +11,16 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -simple-error = "0.2.3" -serde_json = "1.0" -serde = { version = "1.0", features = ["derive"] } +simple-error = "0.3.0" +serde_json = "1.0.107" +serde = { version = "1.0.189", features = ["derive"] } +serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } -aws-sdk-dynamodb = "0.21.0" -aws-config = "0.51.0" -tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +aws-sdk-dynamodb = "0.33.0" +aws-config = "0.56.1" +tokio = { version = "1.33.0", features = ["macros"] } +tracing = { version = "0.1.40", features = ["log"] } +tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 5a7030f9..b2e8af20 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,9 +1,10 @@ -use aws_sdk_dynamodb::model::AttributeValue; -use aws_sdk_dynamodb::{Client, Error as OtherError}; +use aws_sdk_dynamodb::{Client}; use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use serde::{Deserialize, Serialize}; +use serde_dynamo::to_attribute_value; use tracing::info; -#[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { pub p_type: String, pub age: String, @@ -76,12 +77,12 @@ async fn main() -> Result<(), Error> { // Add an item to a table. // snippet-start:[dynamodb.rust.add-item] -pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), OtherError> { - let user_av = AttributeValue::S(item.username); - let type_av = AttributeValue::S(item.p_type); - let age_av = AttributeValue::S(item.age); - let first_av = AttributeValue::S(item.first); - let last_av = AttributeValue::S(item.last); +pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Error> { + let user_av = to_attribute_value(item.username)?; + let type_av = to_attribute_value(item.p_type)?; + let age_av = to_attribute_value(item.age)?; + let first_av = to_attribute_value(item.first)?; + let last_av = to_attribute_value(item.last)?; let request = client .put_item() From 45525e0dfe1196315dd130101b9cec64ac6b67f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Greinhofer?= Date: Mon, 23 Oct 2023 14:52:41 -0500 Subject: [PATCH 029/211] Add SQS API event structs (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds strucs to allow serializing data coming from the AWS SQS API. Fixes awslabs/aws-lambda-rust-runtime#710 Signed-off-by: Rémy Greinhofer --- lambda-events/src/event/sqs/mod.rs | 90 +++++++++++++++++++ .../fixtures/example-sqs-api-event-obj.json | 10 +++ 2 files changed, 100 insertions(+) create mode 100644 lambda-events/src/fixtures/example-sqs-api-event-obj.json diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index af4d3f21..5c10a428 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -112,6 +112,74 @@ pub struct BatchItemFailure { pub item_identifier: String, } +/// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SqsApiEventObj { + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub messages: Vec>, +} + +/// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsApiEvent { + pub messages: Vec, +} + +/// Alternative to SqsApiEvent to be used alongside SqsApiMessageObj when you need to +/// deserialize a nested object into a struct of type T within the SQS Message rather +/// than just using the raw SQS Message string +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "PascalCase")] +pub struct SqsApiMessageObj { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + /// Deserialized into a `T` from nested JSON inside the SQS body string. `T` must implement the `Deserialize` or `DeserializeOwned` trait. + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub body: T, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// An individual SQS API Message, its metadata, and Message Attributes +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SqsApiMessage { + /// nolint: stylecheck + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub receipt_handle: Option, + #[serde(default)] + pub body: Option, + #[serde(default)] + pub md5_of_body: Option, + #[serde(default)] + pub md5_of_message_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + #[cfg(test)] mod test { use super::*; @@ -159,4 +227,26 @@ mod test { let reparsed: SqsBatchResponse = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_api_obj_event() { + // Example sqs api receive message response, fetched 2023-10-23, inspired from: + // https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html#API_ReceiveMessage_ResponseSyntax + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustStruct { + city: String, + country: String, + } + + let data = include_bytes!("../../fixtures/example-sqs-api-event-obj.json"); + let parsed: SqsApiEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.messages[0].body.city, "provincetown"); + assert_eq!(parsed.messages[0].body.country, "usa"); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsApiEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-sqs-api-event-obj.json b/lambda-events/src/fixtures/example-sqs-api-event-obj.json new file mode 100644 index 00000000..39ab67cf --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-api-event-obj.json @@ -0,0 +1,10 @@ +{ + "Messages": [ + { + "Body": "{\"country\": \"usa\", \"city\": \"provincetown\"}", + "Md5OfBody": "2b3e4f40b57e80d67ac5b9660c56d787", + "MessageId": "f663a189-97e2-41f5-9c0e-cfb595d8322c", + "ReceiptHandle": "AQEBdObBZIl7FWJiK9c3KmqKNvusy6+eqG51SLIp5Gs6lQ6+e4SI0lJ6Glw+qcOi+2RRrnfOjlsF8uDlo13TgubmtgP+CH7s+YKDdpbg2jA931vLi6qnU0ZFXcf/H8BDZ4kcz29npMu9/N2DT9F+kI9Q9pTfLsISg/7XFMvRTqAtjSfa2wI5TVcOPZBdkGqTLUoKqAYni0L7NTLzFUTjCN/HiOcvG+16zahhsTniM1MwOTSpbOO2uTZmY25V/PCfNdF1PBXtdNA9mWW2Ym6THV28ug3cuK6dXbFQBuxIGVhOq+mRVU6gKN/eZpZediiBt75oHD6ASu8jIUpJGeUWEZm6qSWU+YTivr6QoqGLwAVvI3CXOIZQ/+Wp/RJAxMQxtRIe/MOsOITcmGlFqhWnjlGQdg==" + } + ] +} From c215812f284e8d53f915518d959af493dce3c490 Mon Sep 17 00:00:00 2001 From: Martin Bartlett Date: Fri, 27 Oct 2023 18:37:38 +0200 Subject: [PATCH 030/211] Remove cfg(test) on with_stage_variables (#713) None of the other with_ methods are constrained to test-only, although this method did have a specific comment indicating test-only, so there may have been a reason. Anyway, this commit removes that constraint. Co-authored-by: Martin Bartlett --- lambda-http/src/ext/extensions.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index e002d0ea..313090c6 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -108,10 +108,9 @@ pub trait RequestExt { /// These will always be `None` for ALB triggered requests. fn stage_variables_ref(&self) -> Option<&QueryMap>; - /// Configures instance with stage variables under `#[cfg(test)]` configurations + /// Configures instance with stage variables /// /// This is intended for use in mock testing contexts. - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into; @@ -216,7 +215,6 @@ impl RequestExt for http::Extensions { .and_then(|StageVariables(vars)| if vars.is_empty() { None } else { Some(vars) }) } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, @@ -318,7 +316,6 @@ impl RequestExt for Parts { self.extensions.stage_variables_ref() } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, @@ -420,7 +417,6 @@ impl RequestExt for http::Request { self.extensions().stage_variables_ref() } - #[cfg(test)] fn with_stage_variables(self, variables: V) -> Self where V: Into, From 3e195f6b0c734ae694cb1375fed57cf1b6dd01fd Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 30 Oct 2023 22:58:22 +0800 Subject: [PATCH 031/211] Fixed media type suffix detection (#714) --- lambda-http/src/response.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index a51d1b2d..e77ec181 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -282,7 +282,9 @@ where } for suffix in TEXT_ENCODING_SUFFIXES { - if content_type.ends_with(suffix) { + let mut parts = content_type.trim().split(';'); + let mime_type = parts.next().unwrap_or_default(); + if mime_type.ends_with(suffix) { return convert_to_text(self, content_type); } } @@ -484,6 +486,24 @@ mod tests { ) } + #[tokio::test] + async fn charset_content_type_header_suffix() { + // Drive the implementation by using `hyper::Body` instead of + // of `aws_lambda_events::encodings::Body` + let response = Response::builder() + .header(CONTENT_TYPE, "application/graphql-response+json; charset=utf-16") + .body(HyperBody::from("000000".as_bytes())) + .expect("unable to build http::Response"); + let response = response.into_response().await; + let response = LambdaResponse::from_response(&RequestOrigin::ApiGatewayV2, response); + + let json = serde_json::to_string(&response).expect("failed to serialize to json"); + assert_eq!( + json, + r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + ) + } + #[tokio::test] async fn content_headers_unset() { // Drive the implementation by using `hyper::Body` instead of From c565173e36891da95218482f10301394dc6b2a97 Mon Sep 17 00:00:00 2001 From: Kikuo Emoto Date: Sun, 5 Nov 2023 03:29:16 +0900 Subject: [PATCH 032/211] Fix: Missing userNotFound field in "create/verify auth challenge" Cognito user pool events (#719) * Add user_not_found to Create/Verify auth challenge events - Adds `user_not_found` field to: - `CognitoEventUserPoolsCreateAuthChallengeRequest` - `CognitoEventUserPoolsVerifyAuthChallengeRequest` - Adds test cases where `user_not_found` becomes `true` for: - `CognitoEventUserPoolsDefineAuthChallengeRequest` - `CognitoEventUserPoolsCreateAuthChallengeRequest` - `CognitoEventUserPoolsVerifyAuthChallengeRequest` issue awslabs/aws-lambda-rust-runtime#718 * Fix coding style with cargo fmt --- lambda-events/src/event/cognito/mod.rs | 46 +++++++++++++++++++ ...-create-auth-challenge-user-not-found.json | 41 +++++++++++++++++ ...event-userpools-create-auth-challenge.json | 3 +- ...-define-auth-challenge-user-not-found.json | 36 +++++++++++++++ ...uth-challenge-optional-answer-correct.json | 3 +- ...-verify-auth-challenge-user-not-found.json | 31 +++++++++++++ ...event-userpools-verify-auth-challenge.json | 3 +- 7 files changed, 160 insertions(+), 3 deletions(-) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 49f2eebd..c07c40a4 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -343,6 +343,8 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters @@ -389,6 +391,8 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters @@ -482,6 +486,20 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_create_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_custommessage() { @@ -518,6 +536,20 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_define_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_migrateuser() { @@ -612,4 +644,18 @@ mod test { let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge_user_not_found() { + let data = + include_bytes!("../../fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json"); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(parsed.request.user_not_found); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json new file mode 100644 index 00000000..40ce2a2b --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge-user-not-found.json @@ -0,0 +1,41 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "CreateAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "challengeName": "CUSTOM_CHALLENGE", + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "publicChallengeParameters": { + "a": "b" + }, + "privateChallengeParameters": { + "c": "d" + }, + "challengeMetadata": "challengeMetadata" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json index 99acf0a2..2d0f2a83 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json @@ -26,7 +26,8 @@ ], "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { "publicChallengeParameters": { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json new file mode 100644 index 00000000..1ad40e2a --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-user-not-found.json @@ -0,0 +1,36 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "DefineAuthChallenge_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "session": [ + { + "challengeName": "PASSWORD_VERIFIER", + "challengeResult": true, + "challengeMetadata": "metadata" + } + ], + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "challengeName": "challengeName", + "issueTokens": true, + "failAuthentication": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json index 70a973f4..f6f7ca09 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json @@ -22,7 +22,8 @@ "challengeAnswer": "123xxxx", "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json new file mode 100644 index 00000000..a5068eaa --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-user-not-found.json @@ -0,0 +1,31 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": true + }, + "response": { + "answerCorrect": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json index b1d88fee..6bff9974 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json @@ -22,7 +22,8 @@ "challengeAnswer": "123xxxx", "clientMetadata": { "exampleMetadataKey": "example metadata value" - } + }, + "userNotFound": false }, "response": { "answerCorrect": true From 2a82ba7b842b9ffbda14fbda4bfbf3aa4a27a157 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 7 Nov 2023 16:44:07 -0500 Subject: [PATCH 033/211] Add an advanced SQS multiple functions with shared data example (#720) * add multi functions example * update Readme * pr comment * pr comments * run clippy --- .../Cargo.toml | 13 ++++ .../README.md | 28 +++++++++ .../consumer/Cargo.toml | 22 +++++++ .../consumer/src/main.rs | 24 ++++++++ .../pizza_lib/Cargo.toml | 7 +++ .../pizza_lib/src/lib.rs | 7 +++ .../producer/Cargo.toml | 25 ++++++++ .../producer/src/main.rs | 61 +++++++++++++++++++ 8 files changed, 187 insertions(+) create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/README.md create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml create mode 100644 examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs diff --git a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml new file mode 100644 index 00000000..116ab8ef --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml @@ -0,0 +1,13 @@ +[workspace] + +members = [ + "producer", + "consumer", + "pizza_lib", +] + +[profile.release] +opt-level = 'z' +lto = true +codegen-units = 1 +panic = 'abort' \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/README.md b/examples/advanced-sqs-multiple-functions-shared-data/README.md new file mode 100644 index 00000000..83136b9b --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/README.md @@ -0,0 +1,28 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +4. Make sure to edit the QUEUE_URL env variable in producer/Cargo.toml +3. Deploy boths functions to AWS Lambda with + +`cargo lambda deploy consumer --iam-role YOUR_ROLE` + +`cargo lambda deploy producer --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` + +## Add the SQS trigger to the consumer function + +You can use aws-cli to create an event source mapping: + +```bash +aws lambda create-event-source-mapping \ +--function-name consumer \ +--region \ +--event-source-arn \ +--batch-size 1 +``` \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml new file mode 100644 index 00000000..8555a073 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "consumer" +version = "0.1.0" +edition = "2021" + + +[dependencies] +#tracing +tracing = "0.1.40" +tracing-subscriber = "0.3.17" + +#aws dependencies +aws-sdk-config = "0.35.0" +aws-sdk-sqs = "0.35.0" +aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } + +#lambda runtime +lambda_runtime = "0.8.1" +tokio = { version = "1", features = ["macros"] } + +#shared lib +pizza_lib = { path = "../pizza_lib" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs new file mode 100644 index 00000000..42290192 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs @@ -0,0 +1,24 @@ +use aws_lambda_events::event::sqs::SqsEventObj; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use pizza_lib::Pizza; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .with_ansi(false) + .without_time() + .init(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(event: LambdaEvent>) -> Result<(), Error> { + for record in event.payload.records.iter() { + let pizza = &record.body; + println!("Pizza name: {} with toppings: {:?}", pizza.name, pizza.toppings); + } + Ok(()) +} diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml new file mode 100644 index 00000000..76631bbd --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "pizza_lib" +version = "0.1.0" +edition = "2021" + +[dependencies] +serde = { version = "1.0.191", features = ["derive"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs new file mode 100644 index 00000000..638fa762 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/src/lib.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize)] +pub struct Pizza { + pub name: String, + pub toppings: Vec, +} diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml new file mode 100644 index 00000000..557ac6e5 --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "producer" +version = "0.1.0" +edition = "2021" + +[package.metadata.lambda.deploy] +env = { "QUEUE_URL" = "https://changeMe" } + +[dependencies] +#tracing +tracing = "0.1.40" +tracing-subscriber = "0.3.17" + +#aws dependencies +aws-config = "0.57.1" +aws-sdk-config = "0.35.0" +aws-sdk-sqs = "0.35.0" + +#lambda runtime +lambda_runtime = "0.8.1" +serde_json = "1.0.108" +tokio = { version = "1", features = ["macros"] } + +#shared lib +pizza_lib = { path = "../pizza_lib" } \ No newline at end of file diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs new file mode 100644 index 00000000..2cc2541b --- /dev/null +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -0,0 +1,61 @@ +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use pizza_lib::Pizza; +use serde_json::{json, Value}; + +struct SQSManager { + client: aws_sdk_sqs::Client, + queue_url: String, +} + +impl SQSManager { + fn new(client: aws_sdk_sqs::Client, queue_url: String) -> Self { + Self { client, queue_url } + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .with_ansi(false) + .without_time() + .init(); + + // read the queue url from the environment + let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); + // build the config from environment variables (fed by AWS Lambda) + let config = aws_config::from_env().load().await; + // create our SQS Manager + let sqs_manager = SQSManager::new(aws_sdk_sqs::Client::new(&config), queue_url); + let sqs_manager_ref = &sqs_manager; + + // no need to create a SQS Client for each incoming request, let's use a shared state + let handler_func_closure = |event: LambdaEvent| async move { + process_event(event, sqs_manager_ref).await + }; + lambda_runtime::run(service_fn(handler_func_closure)).await?; + Ok(()) +} + +async fn process_event(_: LambdaEvent, sqs_manager: &SQSManager) -> Result<(), Error> { + // let's create our pizza + let message = Pizza { + name: "margherita".to_string(), + toppings: vec![ + "San Marzano Tomatoes".to_string(), + "Fresh Mozzarella".to_string(), + "Basil".to_string(), + ], + }; + // send our message to SQS + sqs_manager + .client + .send_message() + .queue_url(&sqs_manager.queue_url) + .message_body(json!(message).to_string()) + .send() + .await?; + + Ok(()) +} From 39f770ba357c6139d3be7b1eceaf426be5f5b547 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 Nov 2023 18:19:11 -0800 Subject: [PATCH 034/211] New runtime, http, and events release. (#721) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index c58ec475..bb7f115e 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.11.1" +version = "0.12.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ea4a5fba..c8caec8d 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.1" +version = "0.8.2" authors = [ "David Calavera ", "Harold Sun ", @@ -41,7 +41,7 @@ percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.11.0" +version = "0.12.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9202b1c1..d16eaedd 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.2" +version = "0.8.3" authors = [ "David Calavera ", "Harold Sun ", From 8e6d77c5cfbbcacbc751758a8d2f1074538797ac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 8 Nov 2023 17:45:54 -0800 Subject: [PATCH 035/211] Fix transitive dependency version of lambda_runtime. (#723) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index c8caec8d..fc93d88f 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.2" +version = "0.8.3" authors = [ "David Calavera ", "Harold Sun ", @@ -29,7 +29,7 @@ futures = "0.3" http = "0.2" http-body = "0.4" hyper = "0.14" -lambda_runtime = { path = "../lambda-runtime", version = "0.8" } +lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" From f102711500e55c0a0bc9e0944f3043604c17db0a Mon Sep 17 00:00:00 2001 From: Kyle Huey Date: Fri, 10 Nov 2023 08:45:20 -0800 Subject: [PATCH 036/211] Use base64 0.21. (#724) * Use base64 0.21. * Use a non-deprecated base64 encoding function. --- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/types.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d16eaedd..9fb8eb8b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -43,5 +43,5 @@ tokio-stream = "0.1.2" lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde_path_to_error = "0.1.11" http-serde = "1.1.3" -base64 = "0.20.0" +base64 = "0.21.0" http-body = "0.4" diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 27a4a9ae..2f0287ee 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,4 +1,5 @@ use crate::{Config, Error}; +use base64::prelude::*; use bytes::Bytes; use http::{HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; @@ -208,7 +209,7 @@ impl ToStreamErrorTrailer for Error { fn to_tailer(&self) -> String { format!( "Lambda-Runtime-Function-Error-Type: Runtime.StreamError\r\nLambda-Runtime-Function-Error-Body: {}\r\n", - base64::encode(self.to_string()) + BASE64_STANDARD.encode(self.to_string()) ) } } From 8cdedc09b89b17c3e505effe83cc74a11ee31347 Mon Sep 17 00:00:00 2001 From: amir-haroun <108406706+amir-haroun@users.noreply.github.com> Date: Fri, 10 Nov 2023 19:08:48 +0100 Subject: [PATCH 037/211] added Default implementation for S3EventRecord (#726) --- lambda-events/src/event/s3/event.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 13d514ad..128a5811 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -13,7 +13,7 @@ pub struct S3Event { } /// `S3EventRecord` which wrap record data -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3EventRecord { #[serde(default)] From 636df70ab5385b7ed741cfeb0a4e4e9d8486ea2e Mon Sep 17 00:00:00 2001 From: Maxime David Date: Sat, 11 Nov 2023 11:29:45 -0500 Subject: [PATCH 038/211] add provided.al2023 integration tests (#727) --- README.md | 4 +- lambda-integration-tests/template.yaml | 86 ++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd7bbcfc..c586e657 100644 --- a/README.md +++ b/README.md @@ -162,7 +162,7 @@ You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip $ aws lambda create-function --function-name rustTest \ --handler bootstrap \ --zip-file fileb://./target/lambda/basic/bootstrap.zip \ - --runtime provided.al2 \ # Change this to provided.al if you would like to use Amazon Linux 1. + --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 (or to provided.al for Amazon Linux 1). --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ --tracing-config Mode=Active @@ -202,7 +202,7 @@ Resources: MemorySize: 128 Architectures: ["arm64"] Handler: bootstrap - Runtime: provided.al2 + Runtime: provided.al2023 Timeout: 5 CodeUri: target/lambda/basic/ diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index d3716d7c..d148c264 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -8,6 +8,17 @@ Globals: Timeout: 5 Resources: + # Rust function using runtime_fn running on AL2023 + RuntimeFnAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-fn/ + Runtime: provided.al2023 + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using runtime_fn running on AL2 RuntimeFnAl2: Type: AWS::Serverless::Function @@ -30,6 +41,17 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using a Service implementation running on AL2023 + RuntimeTraitAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/runtime-trait/ + Runtime: provided.al2023 + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using a Service implementation running on AL2 RuntimeTraitAl2: Type: AWS::Serverless::Function @@ -52,6 +74,38 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using lambda_http::service_fn running on AL2023 + HttpFnAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-fn/ + Runtime: provided.al2023 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2/post + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using lambda_http::service_fn running on AL2 HttpFnAl2: Type: AWS::Serverless::Function @@ -84,6 +138,38 @@ Resources: - !Ref ExtensionFn - !Ref ExtensionTrait + # Rust function using lambda_http with Service running on AL2023 + HttpTraitAl2023: + Type: AWS::Serverless::Function + Properties: + CodeUri: ../build/http-trait/ + Runtime: provided.al2023 + Events: + ApiGet: + Type: Api + Properties: + Method: GET + Path: /al2-trait/get + ApiPost: + Type: Api + Properties: + Method: POST + Path: /al2-trait/post + ApiV2Get: + Type: HttpApi + Properties: + Method: GET + Path: /al2-trait/get + ApiV2Post: + Type: HttpApi + Properties: + Method: POST + Path: /al2-trait/post + Layers: + - !Ref LogsTrait + - !Ref ExtensionFn + - !Ref ExtensionTrait + # Rust function using lambda_http with Service running on AL2 HttpTraitAl2: Type: AWS::Serverless::Function From 25acb4a3fd176698f8c249857d5b6bdb23afd472 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 Nov 2023 16:56:26 -0800 Subject: [PATCH 039/211] Release lambda-events 0.12.1 (#728) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index bb7f115e..ca5fa1a8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.12.0" +version = "0.12.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", From 32207eaa3b4e946144e7e7ee0f654fb88f12ed7e Mon Sep 17 00:00:00 2001 From: Siarhei Kazhura <90936653+kazhura-aws@users.noreply.github.com> Date: Tue, 14 Nov 2023 17:37:35 -0800 Subject: [PATCH 040/211] Added LogBuffering validation. (#729) * Added LogBuffering validation. The validation will provide a meaningful error in case the LogBuffering is not configured according to https://docs.aws.amazon.com/lambda/latest/dg/telemetry-api.html\#telemetry-api-buffering * Amended the code based on the PR review: https://github.com/awslabs/aws-lambda-rust-runtime/pull/729 * fixing formatting and linting issues --- lambda-extension/src/extension.rs | 6 ++ lambda-extension/src/logs.rs | 113 +++++++++++++++++++++++++++++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 52fe5c31..4747b041 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -225,6 +225,9 @@ where if let Some(mut log_processor) = self.logs_processor { trace!("Log processor found"); + + validate_buffering_configuration(self.log_buffering)?; + // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.log_port_number)); let make_service = service_fn(move |_socket: &AddrStream| { @@ -261,6 +264,9 @@ where if let Some(mut telemetry_processor) = self.telemetry_processor { trace!("Telemetry processor found"); + + validate_buffering_configuration(self.telemetry_buffering)?; + // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number)); let make_service = service_fn(move |_socket: &AddrStream| { diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index cf894100..20a095c4 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -5,6 +5,8 @@ use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; +use crate::{Error, ExtensionError}; + /// Payload received from the Lambda Logs API /// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-msg #[derive(Clone, Debug, Deserialize, PartialEq)] @@ -110,7 +112,7 @@ pub struct LogPlatformReportMetrics { /// Log buffering configuration. /// Allows Lambda to buffer logs before deliverying them to a subscriber. -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, Copy, Clone)] #[serde(rename_all = "camelCase")] pub struct LogBuffering { /// The maximum time (in milliseconds) to buffer a batch. @@ -124,6 +126,40 @@ pub struct LogBuffering { pub max_items: usize, } +static LOG_BUFFERING_MIN_TIMEOUT_MS: usize = 25; +static LOG_BUFFERING_MAX_TIMEOUT_MS: usize = 30_000; +static LOG_BUFFERING_MIN_BYTES: usize = 262_144; +static LOG_BUFFERING_MAX_BYTES: usize = 1_048_576; +static LOG_BUFFERING_MIN_ITEMS: usize = 1_000; +static LOG_BUFFERING_MAX_ITEMS: usize = 10_000; + +impl LogBuffering { + fn validate(&self) -> Result<(), Error> { + if self.timeout_ms < LOG_BUFFERING_MIN_TIMEOUT_MS || self.timeout_ms > LOG_BUFFERING_MAX_TIMEOUT_MS { + let error = format!( + "LogBuffering validation error: Invalid timeout_ms: {}. Allowed values: Minumun: {}. Maximum: {}", + self.timeout_ms, LOG_BUFFERING_MIN_TIMEOUT_MS, LOG_BUFFERING_MAX_TIMEOUT_MS + ); + return Err(ExtensionError::boxed(error)); + } + if self.max_bytes < LOG_BUFFERING_MIN_BYTES || self.max_bytes > LOG_BUFFERING_MAX_BYTES { + let error = format!( + "LogBuffering validation error: Invalid max_bytes: {}. Allowed values: Minumun: {}. Maximum: {}", + self.max_bytes, LOG_BUFFERING_MIN_BYTES, LOG_BUFFERING_MAX_BYTES + ); + return Err(ExtensionError::boxed(error)); + } + if self.max_items < LOG_BUFFERING_MIN_ITEMS || self.max_items > LOG_BUFFERING_MAX_ITEMS { + let error = format!( + "LogBuffering validation error: Invalid max_items: {}. Allowed values: Minumun: {}. Maximum: {}", + self.max_items, LOG_BUFFERING_MIN_ITEMS, LOG_BUFFERING_MAX_ITEMS + ); + return Err(ExtensionError::boxed(error)); + } + Ok(()) + } +} + impl Default for LogBuffering { fn default() -> Self { LogBuffering { @@ -134,6 +170,18 @@ impl Default for LogBuffering { } } +/// Validate the `LogBuffering` configuration (if present) +/// +/// # Errors +/// +/// This function will return an error if `LogBuffering` is present and configured incorrectly +pub(crate) fn validate_buffering_configuration(log_buffering: Option) -> Result<(), Error> { + match log_buffering { + Some(log_buffering) => log_buffering.validate(), + None => Ok(()), + } +} + /// Wrapper function that sends logs to the subscriber Service /// /// This takes an `hyper::Request` and transforms it into `Vec` for the @@ -303,4 +351,67 @@ mod tests { }, ), } + + macro_rules! log_buffering_configuration_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let result = validate_buffering_configuration(input); + + if let Some(expected) = expected { + assert!(result.is_err()); + assert_eq!(result.unwrap_err().to_string(), expected.to_string()); + } else { + assert!(result.is_ok()); + } + + } + )* + } + } + + log_buffering_configuration_tests! { + log_buffer_configuration_none_success: ( + None, + None:: + ), + log_buffer_configuration_default_success: ( + Some(LogBuffering::default()), + None:: + ), + log_buffer_configuration_min_success: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MIN_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MIN_BYTES, max_items: LOG_BUFFERING_MIN_ITEMS }), + None:: + ), + log_buffer_configuration_max_success: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + None:: + ), + min_timeout_ms_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MIN_TIMEOUT_MS-1, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid timeout_ms: 24. Allowed values: Minumun: 25. Maximum: 30000")) + ), + max_timeout_ms_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS+1, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid timeout_ms: 30001. Allowed values: Minumun: 25. Maximum: 30000")) + ), + min_max_bytes_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MIN_BYTES-1, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_bytes: 262143. Allowed values: Minumun: 262144. Maximum: 1048576")) + ), + max_max_bytes_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES+1, max_items: LOG_BUFFERING_MAX_ITEMS }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_bytes: 1048577. Allowed values: Minumun: 262144. Maximum: 1048576")) + ), + min_max_items_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MIN_ITEMS-1 }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_items: 999. Allowed values: Minumun: 1000. Maximum: 10000")) + ), + max_max_items_error: ( + Some(LogBuffering { timeout_ms: LOG_BUFFERING_MAX_TIMEOUT_MS, max_bytes: LOG_BUFFERING_MAX_BYTES, max_items: LOG_BUFFERING_MAX_ITEMS+1 }), + Some(ExtensionError::boxed("LogBuffering validation error: Invalid max_items: 10001. Allowed values: Minumun: 1000. Maximum: 10000")) + ), + } } From fed79f4497e4723e63196a5fd2c7cd62ff424817 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 14 Nov 2023 19:27:00 -0800 Subject: [PATCH 041/211] Release lambda_extension 0.8.2 (#730) Fixes incompatibility issue calling the logging api on AL2023. Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index fcae3cc7..5d9d54b4 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.8.1" +version = "0.8.2" edition = "2021" authors = [ "David Calavera ", From d3e365cfe94e51b7b3238b16575d563a5f6f0c10 Mon Sep 17 00:00:00 2001 From: Titus <8200809+nismotie@users.noreply.github.com> Date: Fri, 17 Nov 2023 17:20:33 +0000 Subject: [PATCH 042/211] Fix typo in LogBuffering struct documentation (#731) Co-authored-by: Titus Bridgwood --- lambda-extension/src/logs.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 20a095c4..c453c951 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -111,7 +111,7 @@ pub struct LogPlatformReportMetrics { } /// Log buffering configuration. -/// Allows Lambda to buffer logs before deliverying them to a subscriber. +/// Allows Lambda to buffer logs before delivering them to a subscriber. #[derive(Debug, Serialize, Copy, Clone)] #[serde(rename_all = "camelCase")] pub struct LogBuffering { From 53637e79ef499088007b74e702fb864748622bce Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 20 Nov 2023 10:42:07 -0800 Subject: [PATCH 043/211] Remove function config allocations per invocation. (#732) Every invocation clones the function config. This allocates memory in the heap for no reason. This change removes those extra allocations by wrapping the config into an Arc and sharing that between invocations. Signed-off-by: David Calavera --- Cargo.toml | 1 + lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 28 ++++++++----- lambda-runtime/src/types.rs | 81 ++++++++++++++++++++++--------------- 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 48bcd5db..16f57a7b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "lambda-http", "lambda-integration-tests", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9fb8eb8b..335b5482 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -32,7 +32,7 @@ hyper = { version = "0.14.20", features = [ "server", ] } futures = "0.3" -serde = { version = "1", features = ["derive"] } +serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" bytes = "1.0" http = "0.2" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 18b1066e..5404fb96 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -23,6 +23,7 @@ use std::{ future::Future, marker::PhantomData, panic, + sync::Arc, }; use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; @@ -58,6 +59,8 @@ pub struct Config { pub log_group: String, } +type RefConfig = Arc; + impl Config { /// Attempts to read configuration from environment variables. pub fn from_env() -> Result { @@ -86,7 +89,7 @@ where struct Runtime = HttpConnector> { client: Client, - config: Config, + config: RefConfig, } impl Runtime @@ -127,8 +130,7 @@ where continue; } - let ctx: Context = Context::try_from(parts.headers)?; - let ctx: Context = ctx.with_config(&self.config); + let ctx: Context = Context::try_from((self.config.clone(), parts.headers))?; let request_id = &ctx.request_id.clone(); let request_span = match &ctx.xray_trace_id { @@ -263,7 +265,10 @@ where trace!("Loading config from env"); let config = Config::from_env()?; let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { client, config }; + let runtime = Runtime { + client, + config: Arc::new(config), + }; let client = &runtime.client; let incoming = incoming(client); @@ -294,7 +299,7 @@ mod endpoint_tests { }, simulated, types::Diagnostic, - Error, Runtime, + Config, Error, Runtime, }; use futures::future::BoxFuture; use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; @@ -302,7 +307,7 @@ mod endpoint_tests { use lambda_runtime_api_client::Client; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, marker::PhantomData}; + use std::{convert::TryFrom, env, marker::PhantomData, sync::Arc}; use tokio::{ io::{self, AsyncRead, AsyncWrite}, select, @@ -531,9 +536,12 @@ mod endpoint_tests { if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); } - let config = crate::Config::from_env().expect("Failed to read env vars"); + let config = Config::from_env().expect("Failed to read env vars"); - let runtime = Runtime { client, config }; + let runtime = Runtime { + client, + config: Arc::new(config), + }; let client = &runtime.client; let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; @@ -568,13 +576,13 @@ mod endpoint_tests { let f = crate::service_fn(func); - let config = crate::Config { + let config = Arc::new(Config { function_name: "test_fn".to_string(), memory: 128, version: "1".to_string(), log_stream: "test_stream".to_string(), log_group: "test_log".to_string(), - }; + }); let runtime = Runtime { client, config }; let client = &runtime.client; diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 2f0287ee..a252475b 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,4 +1,4 @@ -use crate::{Config, Error}; +use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; use http::{HeaderMap, HeaderValue, StatusCode}; @@ -97,7 +97,7 @@ pub struct CognitoIdentity { /// are populated using the [Lambda environment variables](https://docs.aws.amazon.com/lambda/latest/dg/current-supported-versions.html) /// and [the headers returned by the poll request to the Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html#runtimes-api-next). #[non_exhaustive] -#[derive(Clone, Debug, Eq, PartialEq, Default, Serialize, Deserialize)] +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct Context { /// The AWS request ID generated by the Lambda service. pub request_id: String, @@ -117,12 +117,14 @@ pub struct Context { /// Lambda function configuration from the local environment variables. /// Includes information such as the function name, memory allocation, /// version, and log streams. - pub env_config: Config, + pub env_config: RefConfig, } -impl TryFrom for Context { +impl TryFrom<(RefConfig, HeaderMap)> for Context { type Error = Error; - fn try_from(headers: HeaderMap) -> Result { + fn try_from(data: (RefConfig, HeaderMap)) -> Result { + let env_config = data.0; + let headers = data.1; let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { serde_json::from_str(value.to_str()?)? } else { @@ -158,13 +160,20 @@ impl TryFrom for Context { .map(|v| String::from_utf8_lossy(v.as_bytes()).to_string()), client_context, identity, - ..Default::default() + env_config, }; Ok(ctx) } } +impl Context { + /// The execution deadline for the current invocation. + pub fn deadline(&self) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) + } +} + /// Incoming Lambda request containing the event payload and context. #[derive(Clone, Debug)] pub struct LambdaEvent { @@ -273,6 +282,8 @@ where #[cfg(test)] mod test { use super::*; + use crate::Config; + use std::sync::Arc; #[test] fn round_trip_lambda_error() { @@ -292,6 +303,8 @@ mod test { #[test] fn context_with_expected_values_and_types_resolves() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -300,16 +313,18 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); } #[test] fn context_with_certain_missing_headers_still_resolves() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); } @@ -338,7 +353,9 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_str(&client_context_str).unwrap(), ); - let tried = Context::try_from(headers); + + let config = Arc::new(Config::default()); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.client_context.is_some()); @@ -347,17 +364,20 @@ mod test { #[test] fn context_with_empty_client_context_resolves() { + let config = Arc::new(Config::default()); let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); assert!(tried.unwrap().client_context.is_some()); } #[test] fn context_with_identity_resolves() { + let config = Arc::new(Config::default()); + let cognito_identity = CognitoIdentity { identity_id: String::new(), identity_pool_id: String::new(), @@ -370,7 +390,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_str(&cognito_identity_str).unwrap(), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.identity.is_some()); @@ -379,6 +399,8 @@ mod test { #[test] fn context_with_bad_deadline_type_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert( @@ -390,12 +412,14 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_bad_client_context_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -403,22 +427,26 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_empty_identity_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}")); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } #[test] fn context_with_bad_identity_is_err() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); @@ -426,7 +454,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from(headers); + let tried = Context::try_from((config, headers)); assert!(tried.is_err()); } @@ -434,6 +462,8 @@ mod test { #[should_panic] #[allow(unused_must_use)] fn context_with_missing_request_id_should_panic() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert( @@ -441,13 +471,15 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from(headers); + Context::try_from((config, headers)); } #[test] #[should_panic] #[allow(unused_must_use)] fn context_with_missing_deadline_should_panic() { + let config = Arc::new(Config::default()); + let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert( @@ -455,21 +487,6 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from(headers); - } -} - -impl Context { - /// Add environment details to the context by setting `env_config`. - pub fn with_config(self, config: &Config) -> Self { - Self { - env_config: config.clone(), - ..self - } - } - - /// The execution deadline for the current invocation. - pub fn deadline(&self) -> SystemTime { - SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) + Context::try_from((config, headers)); } } From b7df6fcf2f3e039cdaaacaffc8aa323f9b4c225e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 28 Nov 2023 09:49:08 -0800 Subject: [PATCH 044/211] Extract the request ID without allocating extra memory. (#735) Changes the way that the Context is initialized to receive the request ID as an argument. This way we also avoid allocating additional memory for it. Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 19 ++------ lambda-runtime/src/types.rs | 93 ++++++++++++++++++++++++------------- 2 files changed, 67 insertions(+), 45 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 5404fb96..ccd35ab0 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -17,7 +17,6 @@ use hyper::{ use lambda_runtime_api_client::Client; use serde::{Deserialize, Serialize}; use std::{ - convert::TryFrom, env, fmt::{self, Debug, Display}, future::Future, @@ -41,6 +40,8 @@ mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; +use types::invoke_request_id; + /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::Error; @@ -121,6 +122,7 @@ where trace!("New event arrived (run loop)"); let event = next_event_response?; let (parts, body) = event.into_parts(); + let request_id = invoke_request_id(&parts.headers)?; #[cfg(debug_assertions)] if parts.status == http::StatusCode::NO_CONTENT { @@ -130,19 +132,8 @@ where continue; } - let ctx: Context = Context::try_from((self.config.clone(), parts.headers))?; - let request_id = &ctx.request_id.clone(); - - let request_span = match &ctx.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!("Lambda runtime invoke", requestId = request_id, xrayTraceId = trace_id) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = request_id) - } - }; + let ctx: Context = Context::new(request_id, self.config.clone(), &parts.headers)?; + let request_span = ctx.request_span(); // Group the handling in one future and instrument it with the span async { diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index a252475b..82d9b21f 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -1,15 +1,16 @@ use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; -use http::{HeaderMap, HeaderValue, StatusCode}; +use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, - convert::TryFrom, + env, fmt::Debug, time::{Duration, SystemTime}, }; use tokio_stream::Stream; +use tracing::Span; #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -120,11 +121,10 @@ pub struct Context { pub env_config: RefConfig, } -impl TryFrom<(RefConfig, HeaderMap)> for Context { - type Error = Error; - fn try_from(data: (RefConfig, HeaderMap)) -> Result { - let env_config = data.0; - let headers = data.1; +impl Context { + /// Create a new [Context] struct based on the fuction configuration + /// and the incoming request data. + pub fn new(request_id: &str, env_config: RefConfig, headers: &HeaderMap) -> Result { let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { serde_json::from_str(value.to_str()?)? } else { @@ -138,11 +138,7 @@ impl TryFrom<(RefConfig, HeaderMap)> for Context { }; let ctx = Context { - request_id: headers - .get("lambda-runtime-aws-request-id") - .expect("missing lambda-runtime-aws-request-id header") - .to_str()? - .to_owned(), + request_id: request_id.to_owned(), deadline: headers .get("lambda-runtime-deadline-ms") .expect("missing lambda-runtime-deadline-ms header") @@ -165,13 +161,37 @@ impl TryFrom<(RefConfig, HeaderMap)> for Context { Ok(ctx) } -} -impl Context { /// The execution deadline for the current invocation. pub fn deadline(&self) -> SystemTime { SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) } + + /// Create a new [`tracing::Span`] for an incoming invocation. + pub(crate) fn request_span(&self) -> Span { + match &self.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!( + "Lambda runtime invoke", + requestId = &self.request_id, + xrayTraceId = trace_id + ) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = &self.request_id) + } + } + } +} + +/// Extract the invocation request id from the incoming request. +pub(crate) fn invoke_request_id(headers: &HeaderMap) -> Result<&str, ToStrError> { + headers + .get("lambda-runtime-aws-request-id") + .expect("missing lambda-runtime-aws-request-id header") + .to_str() } /// Incoming Lambda request containing the event payload and context. @@ -313,7 +333,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); } @@ -324,7 +344,7 @@ mod test { let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); } @@ -355,7 +375,7 @@ mod test { ); let config = Arc::new(Config::default()); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.client_context.is_some()); @@ -369,7 +389,7 @@ mod test { headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-client-context", HeaderValue::from_static("{}")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); assert!(tried.unwrap().client_context.is_some()); } @@ -390,7 +410,7 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_str(&cognito_identity_str).unwrap(), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_ok()); let tried = tried.unwrap(); assert!(tried.identity.is_some()); @@ -412,7 +432,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -427,7 +447,7 @@ mod test { "lambda-runtime-client-context", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -439,7 +459,7 @@ mod test { headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert("lambda-runtime-cognito-identity", HeaderValue::from_static("{}")); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } @@ -454,14 +474,13 @@ mod test { "lambda-runtime-cognito-identity", HeaderValue::from_static("BAD-Type,not JSON"), ); - let tried = Context::try_from((config, headers)); + let tried = Context::new("id", config, &headers); assert!(tried.is_err()); } #[test] #[should_panic] - #[allow(unused_must_use)] - fn context_with_missing_request_id_should_panic() { + fn context_with_missing_deadline_should_panic() { let config = Arc::new(Config::default()); let mut headers = HeaderMap::new(); @@ -471,15 +490,26 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from((config, headers)); + let _ = Context::new("id", config, &headers); } #[test] - #[should_panic] - #[allow(unused_must_use)] - fn context_with_missing_deadline_should_panic() { - let config = Arc::new(Config::default()); + fn invoke_request_id_should_not_panic() { + let mut headers = HeaderMap::new(); + headers.insert("lambda-runtime-aws-request-id", HeaderValue::from_static("my-id")); + headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); + headers.insert( + "lambda-runtime-invoked-function-arn", + HeaderValue::from_static("arn::myarn"), + ); + headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); + + let _ = invoke_request_id(&headers); + } + #[test] + #[should_panic] + fn invoke_request_id_should_panic() { let mut headers = HeaderMap::new(); headers.insert("lambda-runtime-deadline-ms", HeaderValue::from_static("123")); headers.insert( @@ -487,6 +517,7 @@ mod test { HeaderValue::from_static("arn::myarn"), ); headers.insert("lambda-runtime-trace-id", HeaderValue::from_static("arn::myarn")); - Context::try_from((config, headers)); + + let _ = invoke_request_id(&headers); } } From 3c53476206482535f6b73f118e09258aeeb1d6f8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 28 Nov 2023 09:49:50 -0800 Subject: [PATCH 045/211] Remove unused types. (#736) These types are not used anywhere. Signed-off-by: David Calavera --- lambda-runtime/src/types.rs | 28 ---------------------------- 1 file changed, 28 deletions(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 82d9b21f..8b70ce80 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -19,34 +19,6 @@ pub(crate) struct Diagnostic<'a> { pub(crate) error_message: &'a str, } -/// The request ID, which identifies the request that triggered the function invocation. This header -/// tracks the invocation within the Lambda control plane. The request ID is used to specify completion -/// of a given invocation. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct RequestId(pub String); - -/// The date that the function times out in Unix time milliseconds. For example, `1542409706888`. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct InvocationDeadline(pub u64); - -/// The ARN of the Lambda function, version, or alias that is specified in the invocation. -/// For instance, `arn:aws:lambda:us-east-2:123456789012:function:custom-runtime`. -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct FunctionArn(pub String); - -/// The AWS X-Ray Tracing header. For more information, -/// please see [AWS' documentation](https://docs.aws.amazon.com/xray/latest/devguide/xray-concepts.html#xray-concepts-tracingheader). -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct XRayTraceId(pub String); - -/// For invocations from the AWS Mobile SDK contains data about client application and device. -#[derive(Debug, Clone, Eq, PartialEq)] -struct MobileClientContext(String); - -/// For invocations from the AWS Mobile SDK, data about the Amazon Cognito identity provider. -#[derive(Debug, Clone, Eq, PartialEq)] -struct MobileClientIdentity(String); - /// Client context sent by the AWS Mobile SDK. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ClientContext { From 1e4e20326f7df9829550b370901b18579843222b Mon Sep 17 00:00:00 2001 From: Morgan Nicholson <55922364+nichmorgan@users.noreply.github.com> Date: Thu, 30 Nov 2023 22:21:28 -0300 Subject: [PATCH 046/211] Hotfix/documentdb (#742) * Fix field types: - ls_nd must be an AnyDocument - cluster_time must be optional, because it's new at version 4.0 * Fields bug fix --- .../documentdb/events/drop_database_event.rs | 4 +- .../src/event/documentdb/events/drop_event.rs | 3 +- .../event/documentdb/events/insert_event.rs | 2 +- .../event/documentdb/events/replace_event.rs | 2 +- .../event/documentdb/events/update_event.rs | 19 +++++- .../example-documentdb-update-event.json | 60 ++++++++++++------- 6 files changed, 62 insertions(+), 28 deletions(-) diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index c51e345c..273c897c 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -7,7 +7,9 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp pub struct ChangeDropDatabaseEvent { #[serde(rename = "_id")] id: DocumentId, - cluster_time: Timestamp, + #[serde(default)] + cluster_time: Option, + #[serde(default)] #[serde(rename = "lsid")] ls_id: Option, ns: DatabaseCollection, diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index 866ce143..a6f92934 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -6,7 +6,8 @@ use serde::{Deserialize, Serialize}; pub struct ChangeDropEvent { #[serde(rename = "_id")] id: DocumentId, - cluster_time: Timestamp, + #[serde(default)] + cluster_time: Option, #[serde(default)] #[serde(rename = "lsid")] ls_id: Option, diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index 09ab66b2..de9b5843 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -13,7 +13,7 @@ pub struct ChangeInsertEvent { document_key: DocumentKeyId, #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, //operation_type: String, #[serde(default)] diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index 4a0e58ad..c253e272 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -12,7 +12,7 @@ pub struct ChangeReplaceEvent { document_key: DocumentKeyId, #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, // operation_type: String, #[serde(default)] diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 8698485a..04369cf0 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -2,6 +2,21 @@ use serde::{Deserialize, Serialize}; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateTruncate { + field: String, + new_size: usize, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdateDescription { + removed_fields: Vec, + truncated_arrays: Vec, + updated_fields: AnyDocument, +} + #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeUpdateEvent { @@ -10,10 +25,12 @@ pub struct ChangeUpdateEvent { #[serde(default)] cluster_time: Option, document_key: DocumentKeyId, + #[serde(default)] #[serde(rename = "lsid")] - ls_id: Option, + ls_id: Option, ns: DatabaseCollection, // operation_type: String, + update_description: UpdateDescription, #[serde(default)] txn_number: Option, } diff --git a/lambda-events/src/fixtures/example-documentdb-update-event.json b/lambda-events/src/fixtures/example-documentdb-update-event.json index dbb19159..38f3e659 100644 --- a/lambda-events/src/fixtures/example-documentdb-update-event.json +++ b/lambda-events/src/fixtures/example-documentdb-update-event.json @@ -1,29 +1,43 @@ { - "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", - "events": [ - { - "event": { + "eventSourceArn": "arn:aws:rds:us-east-1:123456789012:cluster:canaryclusterb2a659a2-qo5tcmqkcl03", + "events": [ + { + "event": { + "_id": { + "_data": "0163eeb6e7000000090100000009000041e1" + }, + "operationType": "update", + "clusterTime": { + "$timestamp": { + "t": 1676588775, + "i": 9 + } + }, + "ns": { + "db": "test_database", + "coll": "test_collection" + }, + "documentKey": { "_id": { - "_data": "0163eeb6e7000000090100000009000041e1" + "$oid": "63eeb6e7d418cd98afb1c1d7" + } + }, + "updateDescription": { + "updatedFields": { + "email": "alice@10gen.com" }, - "clusterTime": { - "$timestamp": { - "t": 1676588775, - "i": 9 + "removedFields": [ + "phoneNumber" + ], + "truncatedArrays": [ + { + "field": "vacation_time", + "newSize": 36 } - }, - "documentKey": { - "_id": { - "$oid": "63eeb6e7d418cd98afb1c1d7" - } - }, - "ns": { - "db": "test_database", - "coll": "test_collection" - }, - "operationType": "update" + ] } } - ], - "eventSource": "aws:docdb" - } \ No newline at end of file + } + ], + "eventSource": "aws:docdb" +} \ No newline at end of file From 6e953eb6a476f23fad495025bfe2b93467ad7a2c Mon Sep 17 00:00:00 2001 From: David Ramos Date: Sun, 3 Dec 2023 10:22:49 -0800 Subject: [PATCH 047/211] Support internal Lambda extensions (#744) * Support internal Lambda extensions Internal Lambda extensions must be registered during the Lamba lifecycle Init phase, which ends when the runtime calls Next to await the first event. Since the `Extension::run` function both registers and executes the extension, there was previously no way for the runtime to determine that all internal extensions have been registered and that it's safe to proceed to the Invoke lifecycle phase. This change introduces an `Extension::register` method that registers the extension and begins any logs/telemetry handlers. It then returns a new `RegisteredExtension` abstraction that can be used to invoke the extension's run loop concurrently with the runtime's run loop. This change maintains backward compatibility by having the existing `Extension::run` method perform both steps. External Lambda extensions can use either API, and internal extensions should use the new API. Resolves #743. * Add example * Set extension name in example * Remove unnecessary Arc/Mutex --- examples/extension-internal-flush/Cargo.toml | 12 ++ examples/extension-internal-flush/README.md | 30 +++++ examples/extension-internal-flush/src/main.rs | 112 ++++++++++++++++++ lambda-extension/src/extension.rs | 53 ++++++++- 4 files changed, 204 insertions(+), 3 deletions(-) create mode 100644 examples/extension-internal-flush/Cargo.toml create mode 100644 examples/extension-internal-flush/README.md create mode 100644 examples/extension-internal-flush/src/main.rs diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml new file mode 100644 index 00000000..daadd0eb --- /dev/null +++ b/examples/extension-internal-flush/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "extension-internal-flush" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +aws_lambda_events = { path = "../../lambda-events" } +lambda-extension = { path = "../../lambda-extension" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.136" +tokio = { version = "1", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/README.md b/examples/extension-internal-flush/README.md new file mode 100644 index 00000000..553f7a3d --- /dev/null +++ b/examples/extension-internal-flush/README.md @@ -0,0 +1,30 @@ +# AWS Lambda runtime + internal extension example + +This example demonstrates how to build an AWS Lambda function that includes a +[Lambda internal extension](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-extensions-api.html). +Unlike external extensions that run as separate processes, an internal extension runs within the +main runtime process. + +One use case for internal extensions is to flush logs or telemetry data after the Lambda runtime +handler has finished processing an event but before the execution environment is frozen awaiting the +arrival of the next event. Without an explicit flush, telemetry data may never be sent since the +execution environment will remain frozen and eventually be terminated if no additional events arrive. + +Note that for +[synchronous](https://docs.aws.amazon.com/lambda/latest/dg/invocation-sync.html) Lambda invocations +(e.g., via +[Amazon API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-integrations.html)), +the Lambda service returns the response to the caller immediately. Extensions may continue to run +without introducing an observable delay. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the extension with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +The last command will give you an ARN for the extension layer that you can use in your functions. + +## Build for ARM 64 + +Build the extension with `cargo lambda build --release --arm64` diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs new file mode 100644 index 00000000..3706809d --- /dev/null +++ b/examples/extension-internal-flush/src/main.rs @@ -0,0 +1,112 @@ +use anyhow::anyhow; +use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; +use lambda_extension::{service_fn, Error, Extension, NextEvent}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; +use tokio::sync::Mutex; + +use std::sync::Arc; + +/// Implements an internal Lambda extension to flush logs/telemetry after each request. +struct FlushExtension { + request_done_receiver: Mutex>, +} + +impl FlushExtension { + pub fn new(request_done_receiver: UnboundedReceiver<()>) -> Self { + Self { + request_done_receiver: Mutex::new(request_done_receiver), + } + } + + pub async fn invoke(&self, event: lambda_extension::LambdaEvent) -> Result<(), Error> { + match event.next { + // NB: Internal extensions only support the INVOKE event. + NextEvent::Shutdown(shutdown) => { + return Err(anyhow!("extension received unexpected SHUTDOWN event: {:?}", shutdown).into()); + } + NextEvent::Invoke(_e) => {} + } + + eprintln!("[extension] waiting for event to be processed"); + + // Wait for runtime to finish processing event. + self.request_done_receiver + .lock() + .await + .recv() + .await + .ok_or_else(|| anyhow!("channel is closed"))?; + + eprintln!("[extension] flushing logs and telemetry"); + + // + + Ok(()) + } +} + +/// Object that you send to SQS and plan to process with the function. +#[derive(Debug, Deserialize, Serialize)] +struct Data { + a: String, + b: i64, +} + +/// Implements the main event handler for processing events from an SQS queue. +struct EventHandler { + request_done_sender: UnboundedSender<()>, +} + +impl EventHandler { + pub fn new(request_done_sender: UnboundedSender<()>) -> Self { + Self { request_done_sender } + } + + pub async fn invoke( + &self, + event: lambda_runtime::LambdaEvent>, + ) -> Result { + let data = &event.payload.records[0].body; + eprintln!("[runtime] received event {data:?}"); + + // + + // Notify the extension to flush traces. + self.request_done_sender.send(()).map_err(Box::new)?; + + Ok(SqsBatchResponse::default()) + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + let (request_done_sender, request_done_receiver) = unbounded_channel::<()>(); + + let flush_extension = Arc::new(FlushExtension::new(request_done_receiver)); + let extension = Extension::new() + // Internal extensions only support INVOKE events. + .with_events(&["INVOKE"]) + .with_events_processor(service_fn(|event| { + let flush_extension = flush_extension.clone(); + async move { flush_extension.invoke(event).await } + })) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("internal-flush") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await?; + + let handler = Arc::new(EventHandler::new(request_done_sender)); + + tokio::try_join!( + lambda_runtime::run(service_fn(|event| { + let handler = handler.clone(); + async move { handler.invoke(event).await } + })), + extension.run(), + )?; + + Ok(()) +} diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 4747b041..d653e0dc 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -215,14 +215,21 @@ where } } - /// Execute the given extension - pub async fn run(self) -> Result<(), Error> { + /// Register the extension. + /// + /// Performs the + /// [init phase](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-ib) + /// Lambda lifecycle operations to register the extension. When implementing an internal Lambda + /// extension, it is safe to call `lambda_runtime::run` once the future returned by this + /// function resolves. + pub async fn register(self) -> Result, Error> { let client = &Client::builder().build()?; let extension_id = register(client, self.extension_name, self.events).await?; let extension_id = extension_id.to_str()?; - let mut ep = self.events_processor; + // Logs API subscriptions must be requested during the Lambda init phase (see + // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-subscribing). if let Some(mut log_processor) = self.logs_processor { trace!("Log processor found"); @@ -262,6 +269,8 @@ where trace!("Registered extension with Logs API"); } + // Telemetry API subscriptions must be requested during the Lambda init phase (see + // https://docs.aws.amazon.com/lambda/latest/dg/telemetry-api.html#telemetry-api-registration if let Some(mut telemetry_processor) = self.telemetry_processor { trace!("Telemetry processor found"); @@ -301,6 +310,42 @@ where trace!("Registered extension with Telemetry API"); } + Ok(RegisteredExtension { + extension_id: extension_id.to_string(), + events_processor: self.events_processor, + }) + } + + /// Execute the given extension. + pub async fn run(self) -> Result<(), Error> { + self.register().await?.run().await + } +} + +/// An extension registered by calling [`Extension::register`]. +pub struct RegisteredExtension { + extension_id: String, + events_processor: E, +} + +impl RegisteredExtension +where + E: Service, + E::Future: Future>, + E::Error: Into> + fmt::Display + fmt::Debug, +{ + /// Execute the extension's run loop. + /// + /// Performs the + /// [invoke](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-invoke) + /// and, for external Lambda extensions registered to receive the `SHUTDOWN` event, the + /// [shutdown](https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtime-environment.html#runtimes-lifecycle-shutdown) + /// Lambda lifecycle phases. + pub async fn run(self) -> Result<(), Error> { + let client = &Client::builder().build()?; + let mut ep = self.events_processor; + let extension_id = &self.extension_id; + let incoming = async_stream::stream! { loop { trace!("Waiting for next event (incoming loop)"); @@ -351,6 +396,8 @@ where return Err(err.into()); } } + + // Unreachable. Ok(()) } } From 3231e9f144d73b56759d1acbd8e243d283657ca0 Mon Sep 17 00:00:00 2001 From: KUrushi <36495149+KUrushi@users.noreply.github.com> Date: Tue, 5 Dec 2023 01:24:07 +0900 Subject: [PATCH 048/211] add lambda event agent for amazon bedrock (#746) * add lambda event agent for amazon bedrock * add comment for properties * add comment for AgentEvent * add check-event-features for bedrock * remove duplicated module * fix invalid naming * apply formatting --- Makefile | 1 + lambda-events/Cargo.toml | 2 + .../src/event/bedrock_agent_runtime/mod.rs | 94 +++++++++++++++++++ lambda-events/src/event/mod.rs | 4 + .../example-bedrock-agent-runtime-event.json | 40 ++++++++ 5 files changed, 141 insertions(+) create mode 100644 lambda-events/src/event/bedrock_agent_runtime/mod.rs create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json diff --git a/Makefile b/Makefile index 76e57e94..049be8f8 100644 --- a/Makefile +++ b/Makefile @@ -69,6 +69,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features apigw cargo test --package aws_lambda_events --no-default-features --features appsync cargo test --package aws_lambda_events --no-default-features --features autoscaling + cargo test --package aws_lambda_events --no-default-features --features bedrock_agent_runtime cargo test --package aws_lambda_events --no-default-features --features chime_bot cargo test --package aws_lambda_events --no-default-features --features clientvpn cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index ca5fa1a8..b35809a2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -43,6 +43,7 @@ default = [ "apigw", "appsync", "autoscaling", + "bedrock_agent_runtime", "chime_bot", "clientvpn", "cloudformation", @@ -85,6 +86,7 @@ alb = ["bytes", "http", "http-body", "http-serde", "query_map"] apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] appsync = [] autoscaling = ["chrono"] +bedrock_agent_runtime = [] chime_bot = ["chrono"] clientvpn = [] cloudformation = [] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs new file mode 100644 index 00000000..65ab4a9d --- /dev/null +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -0,0 +1,94 @@ +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// The Event sent to Lambda from Agents for Amazon Bedrock. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AgentEvent { + ///The version of the message that identifies the format of the event data going into the Lambda function and the expected format of the response from a Lambda function. Amazon Bedrock only supports version 1.0. + pub message_version: String, + ///Contains information about the name, ID, alias, and version of the agent that the action group belongs to. + pub agent: Agent, + ///The user input for the conversation turn. + pub input_text: String, + /// The unique identifier of the agent session. + pub session_id: String, + /// The name of the action group. + pub action_group: String, + /// The path to the API operation, as defined in the OpenAPI schema. + pub api_path: String, + /// The method of the API operation, as defined in the OpenAPI schema. + pub http_method: String, + /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema. + pub parameters: Vec, + /// Contains the request body and its properties, as defined in the OpenAPI schema. + pub request_body: RequestBody, + /// Contains session attributes and their values. + pub session_attributes: HashMap, + /// Contains prompt attributes and their values. + pub prompt_session_attributes: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RequestBody { + /// Contains the request body and its properties + pub content: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Content { + /// The content of the request body + pub properties: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Property { + /// The name of the parameter + pub name: String, + /// The type of the parameter + pub r#type: String, + /// The value of the parameter + pub value: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Parameter { + /// The name of the parameter + pub name: String, + /// The type of the parameter + pub r#type: String, + /// The value of the parameter + pub value: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Agent { + /// The name of the agent. + pub name: String, + /// The unique identifier of the agent. + pub id: String, + /// The alias of the agent. + pub alias: String, + /// The version of the agent. + pub version: String, +} + +#[cfg(test)] +mod tests { + use serde_json; + + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent__runtime_event() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 5ee57911..28a0c82b 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -17,6 +17,10 @@ pub mod appsync; #[cfg(feature = "autoscaling")] pub mod autoscaling; +/// AWS Lambda event definitions for agent for amazon bedrock +#[cfg(feature = "bedrock_agent_runtime")] +pub mod bedrock_agent_runtime; + /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] pub mod chime_bot; diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json new file mode 100644 index 00000000..b5244515 --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json @@ -0,0 +1,40 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "parameters": [ + { + "name": "param1", + "type": "string", + "value": "value1" + } + ], + "requestBody": { + "content": { + "application/json": { + "properties": [ + { + "name": "prop1", + "type": "string", + "value": "value1" + } + ] + } + } + }, + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file From d513b13b4c48122602c0690f55147607f3bcc0da Mon Sep 17 00:00:00 2001 From: KUrushi <36495149+KUrushi@users.noreply.github.com> Date: Thu, 7 Dec 2023 01:43:17 +0900 Subject: [PATCH 049/211] Made some properties of Bedrock's Event optional. (#748) * change some properties to optional value * Update lambda-events/src/event/bedrock_agent_runtime/mod.rs add default value to parameters Co-authored-by: David Calavera * Update lambda-events/src/event/bedrock_agent_runtime/mod.rs add default to request_body Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- .../src/event/bedrock_agent_runtime/mod.rs | 24 ++++++++++++-- ...gent-runtime-event-without-parameters.json | 33 +++++++++++++++++++ ...nt-runtime-event-without-request-body.json | 27 +++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json create mode 100644 lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index 65ab4a9d..836d2f95 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -20,9 +20,11 @@ pub struct AgentEvent { /// The method of the API operation, as defined in the OpenAPI schema. pub http_method: String, /// Contains a list of objects. Each object contains the name, type, and value of a parameter in the API operation, as defined in the OpenAPI schema. - pub parameters: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub parameters: Option>, /// Contains the request body and its properties, as defined in the OpenAPI schema. - pub request_body: RequestBody, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub request_body: Option, /// Contains session attributes and their values. pub session_attributes: HashMap, /// Contains prompt attributes and their values. @@ -91,4 +93,22 @@ mod tests { let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent_runtime_event_without_parameters() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "bedrock-agent-runtime")] + fn example_bedrock_agent_runtime_event_without_request_body() { + let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); + let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json new file mode 100644 index 00000000..d41d9ee1 --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-parameters.json @@ -0,0 +1,33 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "requestBody": { + "content": { + "application/json": { + "properties": [ + { + "name": "prop1", + "type": "string", + "value": "value1" + } + ] + } + } + }, + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json new file mode 100644 index 00000000..2ca5aa9a --- /dev/null +++ b/lambda-events/src/fixtures/example-bedrock-agent-runtime-event-without-request-body.json @@ -0,0 +1,27 @@ +{ + "messageVersion": "1.0", + "agent": { + "name": "AgentName", + "id": "AgentID", + "alias": "AgentAlias", + "version": "AgentVersion" + }, + "inputText": "InputText", + "sessionId": "SessionID", + "actionGroup": "ActionGroup", + "apiPath": "/api/path", + "httpMethod": "POST", + "parameters": [ + { + "name": "param1", + "type": "string", + "value": "value1" + } + ], + "sessionAttributes": { + "attr1": "value1" + }, + "promptSessionAttributes": { + "promptAttr1": "value1" + } +} \ No newline at end of file From 9b21cd01445675d9e66ada94204adfbeaca3702a Mon Sep 17 00:00:00 2001 From: David Ramos Date: Wed, 13 Dec 2023 18:56:07 -0800 Subject: [PATCH 050/211] Remove #[serde(deny_unknown_fields)] (#753) Adding new fields to request payloads is typically not considered to be a breaking change, but using #[serde(deny_unknown_fields)] would cause all Lambda functions to start failing immediately in production if a new feature were deployed that added a new request field. Fixes #750. --- lambda-events/src/event/alb/mod.rs | 1 - lambda-events/src/event/apigw/mod.rs | 3 --- lambda-events/src/event/cloudformation/mod.rs | 4 ---- lambda-http/src/deserializer.rs | 2 +- 4 files changed, 1 insertion(+), 9 deletions(-) diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 7bb1eb7f..259dce23 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -9,7 +9,6 @@ use serde::{Deserialize, Serialize}; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct AlbTargetGroupRequest { #[serde(with = "http_method")] pub http_method: Method, diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 9119bdc7..fcc5ed0c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,7 +13,6 @@ use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayProxyRequest where T1: DeserializeOwned, @@ -120,7 +119,6 @@ where /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayV2httpRequest { #[serde(default, rename = "type")] pub kind: Option, @@ -335,7 +333,6 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -#[serde(deny_unknown_fields)] pub struct ApiGatewayWebsocketProxyRequest where T1: DeserializeOwned, diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index e2d51745..0d3f816c 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -18,7 +18,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct CreateRequest where P2: DeserializeOwned + Serialize, @@ -37,7 +36,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct UpdateRequest where P1: DeserializeOwned + Serialize, @@ -60,7 +58,6 @@ where #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] -#[serde(deny_unknown_fields)] pub struct DeleteRequest where P2: DeserializeOwned + Serialize, @@ -87,7 +84,6 @@ mod test { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] - #[serde(deny_unknown_fields)] struct TestProperties { key_1: String, key_2: Vec, diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index a77f68a5..7756629c 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -107,7 +107,7 @@ mod tests { #[test] fn test_deserialize_error() { - let err = serde_json::from_str::("{\"command\": \"hi\"}").unwrap_err(); + let err = serde_json::from_str::("{\"body\": {}}").unwrap_err(); assert_eq!(ERROR_CONTEXT, err.to_string()); } From 2a2ae8bad6913d1af12e25e8fa60af8da21ff443 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Fri, 15 Dec 2023 10:55:41 +0900 Subject: [PATCH 051/211] Add session_issuer field to SessionContext (#751) * Add session_issuer field to SessionContext * Update lambda-events/src/event/s3/object_lambda.rs Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- lambda-events/src/event/s3/object_lambda.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 1eec2c0d..69e9907d 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -102,6 +102,8 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct SessionContext { pub attributes: HashMap, + #[serde(default)] + pub session_issuer: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] From fb86ebc1fdb680107165f081a36863574de40917 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 15 Dec 2023 17:26:50 -0800 Subject: [PATCH 052/211] Config::from_env never returns an error. (#754) We don't need to return Result because the function either panics or returns the Ok value. Signed-off-by: David Calavera --- lambda-runtime/src/lib.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index ccd35ab0..49add76c 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -64,8 +64,8 @@ type RefConfig = Arc; impl Config { /// Attempts to read configuration from environment variables. - pub fn from_env() -> Result { - let conf = Config { + pub fn from_env() -> Self { + Config { function_name: env::var("AWS_LAMBDA_FUNCTION_NAME").expect("Missing AWS_LAMBDA_FUNCTION_NAME env var"), memory: env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE") .expect("Missing AWS_LAMBDA_FUNCTION_MEMORY_SIZE env var") @@ -74,8 +74,7 @@ impl Config { version: env::var("AWS_LAMBDA_FUNCTION_VERSION").expect("Missing AWS_LAMBDA_FUNCTION_VERSION env var"), log_stream: env::var("AWS_LAMBDA_LOG_STREAM_NAME").unwrap_or_default(), log_group: env::var("AWS_LAMBDA_LOG_GROUP_NAME").unwrap_or_default(), - }; - Ok(conf) + } } } @@ -254,7 +253,7 @@ where E: Into + Send + Debug, { trace!("Loading config from env"); - let config = Config::from_env()?; + let config = Config::from_env(); let client = Client::builder().build().expect("Unable to create a runtime client"); let runtime = Runtime { client, @@ -527,7 +526,7 @@ mod endpoint_tests { if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); } - let config = Config::from_env().expect("Failed to read env vars"); + let config = Config::from_env(); let runtime = Runtime { client, From 07430f1e174ab81477df40a42325f0925036ee7b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Dec 2023 08:30:50 -0800 Subject: [PATCH 053/211] Hyper 1 upgrade (#749) * chore: update hyper/http * chore: update hyper with legacy * Create new workspace dependencies. This makes tracking versions across packages easier. Signed-off-by: David Calavera * Bring helpers to work with Hyper 1. Copy some of the utils that Axum created to support Hyper 1. Signed-off-by: David Calavera * Upgrade lambda_runtime to Hyper 1. Signed-off-by: David Calavera * Upgrade lambda_http to Hyper 1. Signed-off-by: David Calavera * Update Axum examples to version 0.7. Signed-off-by: David Calavera * Make extensions compile. Switch to use hyper::service::service_fn definitions. Implement new Hyper's polling loop for http connections. Signed-off-by: David Calavera * Update tower-http to make cors example work. Signed-off-by: David Calavera * Update tower-http to make tower-trace example work. Signed-off-by: David Calavera * Make less copies of extension services. Signed-off-by: David Calavera * Bring Body::channel implementation from Hyper Given the lack of a public implementation anymore, we don't have other choice but copying Hyper's private implementation. Signed-off-by: David Calavera * Organize more dependencies. Signed-off-by: David Calavera * Cleanup commented code. Signed-off-by: David Calavera * Cleanup unused dependencies. Signed-off-by: David Calavera * Introduce a module to group all streaming functionality. This makes the streaming functionallity more concise. It aliases other functionality to keep backwards compatibility. Signed-off-by: David Calavera * Remove unnecesary loop. Signed-off-by: David Calavera * Don't re-export `lambda_runtime_api_client::body`. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera Co-authored-by: Spencer C. Imbleau --- Cargo.toml | 16 ++ examples/basic-streaming-response/Cargo.toml | 5 - examples/basic-streaming-response/src/main.rs | 16 +- examples/http-axum-diesel-ssl/Cargo.toml | 2 +- examples/http-axum-diesel/Cargo.toml | 2 +- examples/http-axum/Cargo.toml | 5 +- examples/http-cors/Cargo.toml | 2 +- examples/http-tower-trace/Cargo.toml | 2 +- lambda-events/Cargo.toml | 20 +-- lambda-events/src/encodings/http.rs | 31 ++-- .../src/event/bedrock_agent_runtime/mod.rs | 2 - lambda-extension/Cargo.toml | 16 +- lambda-extension/src/error.rs | 2 +- lambda-extension/src/extension.rs | 95 +++++++----- lambda-extension/src/logs.rs | 23 +-- lambda-extension/src/requests.rs | 3 +- lambda-extension/src/telemetry.rs | 18 ++- lambda-http/Cargo.toml | 22 +-- lambda-http/src/ext/extensions.rs | 4 + lambda-http/src/response.rs | 16 +- lambda-http/src/streaming.rs | 24 ++- lambda-runtime-api-client/Cargo.toml | 20 ++- lambda-runtime-api-client/src/body/channel.rs | 110 ++++++++++++++ lambda-runtime-api-client/src/body/mod.rs | 143 ++++++++++++++++++ lambda-runtime-api-client/src/body/sender.rs | 135 +++++++++++++++++ lambda-runtime-api-client/src/body/watch.rs | 69 +++++++++ lambda-runtime-api-client/src/error.rs | 33 ++++ lambda-runtime-api-client/src/lib.rs | 36 ++--- lambda-runtime/Cargo.toml | 51 ++++--- lambda-runtime/src/lib.rs | 97 ++++++------ lambda-runtime/src/requests.rs | 32 ++-- lambda-runtime/src/simulated.rs | 79 +++++++--- lambda-runtime/src/streaming.rs | 35 +++++ lambda-runtime/src/types.rs | 19 ++- 34 files changed, 921 insertions(+), 264 deletions(-) create mode 100644 lambda-runtime-api-client/src/body/channel.rs create mode 100644 lambda-runtime-api-client/src/body/mod.rs create mode 100644 lambda-runtime-api-client/src/body/sender.rs create mode 100644 lambda-runtime-api-client/src/body/watch.rs create mode 100644 lambda-runtime-api-client/src/error.rs create mode 100644 lambda-runtime/src/streaming.rs diff --git a/Cargo.toml b/Cargo.toml index 16f57a7b..51d57ff4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,3 +10,19 @@ members = [ ] exclude = ["examples"] + +[workspace.dependencies] +base64 = "0.21" +bytes = "1" +futures = "0.3" +futures-channel = "0.3" +futures-util = "0.3" +http = "1.0" +http-body = "1.0" +http-body-util = "0.1" +http-serde = "2.0" +hyper = "1.0" +hyper-util = "0.1.1" +pin-project-lite = "0.2" +tower = "0.4" +tower-service = "0.3" diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index 4bbe66f4..e9b7499c 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -6,11 +6,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -hyper = { version = "0.14", features = [ - "http1", - "client", - "stream", -] } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 9d505206..c8932554 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,12 +1,15 @@ -use hyper::body::Body; -use lambda_runtime::{service_fn, Error, LambdaEvent, StreamResponse}; +use lambda_runtime::{ + service_fn, + streaming::{channel, Body, Response}, + Error, LambdaEvent, +}; use serde_json::Value; use std::{thread, time::Duration}; -async fn func(_event: LambdaEvent) -> Result, Error> { +async fn func(_event: LambdaEvent) -> Result, Error> { let messages = vec!["Hello", "world", "from", "Lambda!"]; - let (mut tx, rx) = Body::channel(); + let (mut tx, rx) = channel(); tokio::spawn(async move { for message in messages.iter() { @@ -15,10 +18,7 @@ async fn func(_event: LambdaEvent) -> Result, Error> } }); - Ok(StreamResponse { - metadata_prelude: Default::default(), - stream: rx, - }) + Ok(Response::from(rx)) } #[tokio::main] diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index cdcdd4ef..006a82ce 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -axum = "0.6.4" +axum = "0.7" bb8 = "0.8.0" diesel = "2.0.3" diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 5a97cfab..0366f32d 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -11,7 +11,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -axum = "0.6.4" +axum = "0.7" bb8 = "0.8.0" diesel = "2.0.3" diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 50db3ebf..88df4140 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -11,11 +11,10 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] +axum = "0.7" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - -axum = "0.6.4" -serde_json = "1.0" diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 9fd7f25b..059a3f63 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -14,7 +14,7 @@ edition = "2021" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.3.3", features = ["cors"] } +tower-http = { version = "0.5", features = ["cors"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 2b8f7a60..0b0c46a9 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -14,6 +14,6 @@ edition = "2021" lambda_http = { path = "../../lambda-http" } lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.3.4", features = ["trace"] } +tower-http = { version = "0.5", features = ["trace"] } tracing = { version = "0.1", features = ["log"] } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b35809a2..29d4e191 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -16,25 +16,25 @@ categories = ["api-bindings", "encoding", "web-programming"] edition = "2021" [dependencies] -base64 = "0.21" -http = { version = "0.2", optional = true } -http-body = { version = "0.4", optional = true } -http-serde = { version = "^1", optional = true } -serde = { version = "^1", features = ["derive"] } -serde_with = { version = "^3", features = ["json"], optional = true } -serde_json = "^1" -serde_dynamo = { version = "^4.1", optional = true } -bytes = { version = "1", features = ["serde"], optional = true } +base64 = { workspace = true } +bytes = { workspace = true, features = ["serde"], optional = true } chrono = { version = "0.4.31", default-features = false, features = [ "clock", "serde", "std", ], optional = true } +flate2 = { version = "1.0.24", optional = true } +http = { workspace = true, optional = true } +http-body = { workspace = true, optional = true } +http-serde = { workspace = true, optional = true } query_map = { version = "^0.7", features = [ "serde", "url-query", ], optional = true } -flate2 = { version = "1.0.24", optional = true } +serde = { version = "^1", features = ["derive"] } +serde_with = { version = "^3", features = ["json"], optional = true } +serde_json = "^1" +serde_dynamo = { version = "^4.1", optional = true } [features] default = [ diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index effb48f4..1cb10c81 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -218,25 +218,6 @@ impl HttpBody for Body { type Data = Bytes; type Error = super::Error; - fn poll_data( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll>> { - let body = take(self.get_mut()); - Poll::Ready(match body { - Body::Empty => None, - Body::Text(s) => Some(Ok(s.into())), - Body::Binary(b) => Some(Ok(b.into())), - }) - } - - fn poll_trailers( - self: Pin<&mut Self>, - _cx: &mut std::task::Context<'_>, - ) -> Poll, Self::Error>> { - Poll::Ready(Ok(None)) - } - fn is_end_stream(&self) -> bool { matches!(self, Body::Empty) } @@ -248,6 +229,18 @@ impl HttpBody for Body { Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), } } + + fn poll_frame( + self: Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + ) -> Poll, Self::Error>>> { + let body = take(self.get_mut()); + Poll::Ready(match body { + Body::Empty => None, + Body::Text(s) => Some(Ok(http_body::Frame::data(s.into()))), + Body::Binary(b) => Some(Ok(http_body::Frame::data(b.into()))), + }) + } } #[cfg(test)] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index 836d2f95..cf84d4d3 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -82,8 +82,6 @@ pub struct Agent { #[cfg(test)] mod tests { - use serde_json; - #[test] #[cfg(feature = "bedrock-agent-runtime")] fn example_bedrock_agent__runtime_event() { diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 5d9d54b4..667b866f 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -15,15 +15,21 @@ readme = "README.md" [dependencies] async-stream = "0.3" -bytes = "1.0" +bytes = { workspace = true } chrono = { version = "0.4", features = ["serde"] } -http = "0.2" -hyper = { version = "0.14.20", features = ["http1", "client", "server", "stream", "runtime"] } +http = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true, features = ["http1", "client", "server"] } +hyper-util = { workspace = true } lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } -tokio = { version = "1.0", features = ["macros", "io-util", "sync", "rt-multi-thread"] } +tokio = { version = "1.0", features = [ + "macros", + "io-util", + "sync", + "rt-multi-thread", +] } tokio-stream = "0.1.2" tower = { version = "0.4", features = ["make", "util"] } - diff --git a/lambda-extension/src/error.rs b/lambda-extension/src/error.rs index 2c3e23b3..4f6a9909 100644 --- a/lambda-extension/src/error.rs +++ b/lambda-extension/src/error.rs @@ -1,5 +1,5 @@ /// Error type that extensions may result in -pub type Error = lambda_runtime_api_client::Error; +pub type Error = lambda_runtime_api_client::BoxError; /// Simple error that encapsulates human readable descriptions #[derive(Clone, Debug, PartialEq, Eq)] diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index d653e0dc..cac1c7ec 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,13 +1,18 @@ +use http::Request; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use hyper::server::conn::http1; +use hyper::service::service_fn; + +use hyper_util::rt::tokio::TokioIo; +use lambda_runtime_api_client::Client; use std::{ convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, }; - -use hyper::{server::conn::AddrStream, Server}; -use lambda_runtime_api_client::Client; -use tokio::sync::Mutex; +use tokio::{net::TcpListener, sync::Mutex}; use tokio_stream::StreamExt; -use tower::{service_fn, MakeService, Service, ServiceExt}; -use tracing::{error, trace}; +use tower::{MakeService, Service, ServiceExt}; +use tracing::trace; use crate::{ logs::*, @@ -64,22 +69,22 @@ impl<'a, E, L, T> Extension<'a, E, L, T> where E: Service, E::Future: Future>, - E::Error: Into> + fmt::Display + fmt::Debug, + E::Error: Into + fmt::Display + fmt::Debug, // Fixme: 'static bound might be too restrictive L: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, L::Service: Service, Response = ()> + Send + Sync, >>::Future: Send + 'a, - L::Error: Into> + fmt::Debug, - L::MakeError: Into> + fmt::Debug, + L::Error: Into + fmt::Debug, + L::MakeError: Into + fmt::Debug, L::Future: Send, // Fixme: 'static bound might be too restrictive T: MakeService<(), Vec, Response = ()> + Send + Sync + 'static, T::Service: Service, Response = ()> + Send + Sync, >>::Future: Send + 'a, - T::Error: Into> + fmt::Debug, - T::MakeError: Into> + fmt::Debug, + T::Error: Into + fmt::Debug, + T::MakeError: Into + fmt::Debug, T::Future: Send, { /// Create a new [`Extension`] with a given extension name @@ -104,7 +109,7 @@ where where N: Service, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { events_processor: ep, @@ -126,7 +131,7 @@ where where N: Service<()>, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { logs_processor: Some(lp), @@ -173,7 +178,7 @@ where where N: Service<()>, N::Future: Future>, - N::Error: Into> + fmt::Display, + N::Error: Into + fmt::Display, { Extension { telemetry_processor: Some(lp), @@ -235,22 +240,27 @@ where validate_buffering_configuration(self.log_buffering)?; - // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.log_port_number)); - let make_service = service_fn(move |_socket: &AddrStream| { - trace!("Creating new log processor Service"); - let service = log_processor.make_service(()); - async move { - let service = Arc::new(Mutex::new(service.await?)); - Ok::<_, L::MakeError>(service_fn(move |req| log_wrapper(service.clone(), req))) - } - }); - let server = Server::bind(&addr).serve(make_service); - tokio::spawn(async move { - if let Err(e) = server.await { - error!("Error while running log processor: {}", e); + let service = log_processor.make_service(()); + let service = Arc::new(Mutex::new(service.await.unwrap())); + tokio::task::spawn(async move { + trace!("Creating new logs processor Service"); + + loop { + let service: Arc> = service.clone(); + let make_service = service_fn(move |req: Request| log_wrapper(service.clone(), req)); + + let listener = TcpListener::bind(addr).await.unwrap(); + let (tcp, _) = listener.accept().await.unwrap(); + let io = TokioIo::new(tcp); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { + println!("Error serving connection: {:?}", err); + } + }); } }); + trace!("Log processor started"); // Call Logs API to start receiving events @@ -276,22 +286,27 @@ where validate_buffering_configuration(self.telemetry_buffering)?; - // Spawn task to run processor let addr = SocketAddr::from(([0, 0, 0, 0], self.telemetry_port_number)); - let make_service = service_fn(move |_socket: &AddrStream| { + let service = telemetry_processor.make_service(()); + let service = Arc::new(Mutex::new(service.await.unwrap())); + tokio::task::spawn(async move { trace!("Creating new telemetry processor Service"); - let service = telemetry_processor.make_service(()); - async move { - let service = Arc::new(Mutex::new(service.await?)); - Ok::<_, T::MakeError>(service_fn(move |req| telemetry_wrapper(service.clone(), req))) - } - }); - let server = Server::bind(&addr).serve(make_service); - tokio::spawn(async move { - if let Err(e) = server.await { - error!("Error while running telemetry processor: {}", e); + + loop { + let service = service.clone(); + let make_service = service_fn(move |req| telemetry_wrapper(service.clone(), req)); + + let listener = TcpListener::bind(addr).await.unwrap(); + let (tcp, _) = listener.accept().await.unwrap(); + let io = TokioIo::new(tcp); + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { + println!("Error serving connection: {:?}", err); + } + }); } }); + trace!("Telemetry processor started"); // Call Telemetry API to start receiving events @@ -361,7 +376,7 @@ where let event = event?; let (_parts, body) = event.into_parts(); - let body = hyper::body::to_bytes(body).await?; + let body = body.collect().await?.to_bytes(); trace!("{}", std::str::from_utf8(&body)?); // this may be very verbose let event: NextEvent = serde_json::from_slice(&body)?; let is_invoke = event.is_invoke(); diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index c453c951..4d1948a0 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -1,6 +1,10 @@ use chrono::{DateTime, Utc}; +use http::{Request, Response}; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; -use std::{boxed::Box, fmt, sync::Arc}; +use std::{fmt, sync::Arc}; use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; @@ -186,34 +190,31 @@ pub(crate) fn validate_buffering_configuration(log_buffering: Option` for the /// underlying `Service` to process. -pub(crate) async fn log_wrapper( - service: Arc>, - req: hyper::Request, -) -> Result, Box> +pub(crate) async fn log_wrapper(service: Arc>, req: Request) -> Result, Error> where S: Service, Response = ()>, - S::Error: Into> + fmt::Debug, + S::Error: Into + fmt::Debug, S::Future: Send, { trace!("Received logs request"); // Parse the request body as a Vec - let body = match hyper::body::to_bytes(req.into_body()).await { + let body = match req.into_body().collect().await { Ok(body) => body, Err(e) => { error!("Error reading logs request body: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; - let logs: Vec = match serde_json::from_slice(&body) { + let logs: Vec = match serde_json::from_slice(&body.to_bytes()) { Ok(logs) => logs, Err(e) => { error!("Error parsing logs: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; @@ -226,7 +227,7 @@ where } } - Ok(hyper::Response::new(hyper::Body::empty())) + Ok(hyper::Response::new(Body::empty())) } #[cfg(test)] diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 75c24a0f..4d5f1527 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -1,7 +1,6 @@ use crate::{Error, LogBuffering}; use http::{Method, Request}; -use hyper::Body; -use lambda_runtime_api_client::build_request; +use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; const EXTENSION_NAME_HEADER: &str = "Lambda-Extension-Name"; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index b3131338..1e83ee8e 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -1,4 +1,8 @@ use chrono::{DateTime, Utc}; +use http::{Request, Response}; +use http_body_util::BodyExt; +use hyper::body::Incoming; +use lambda_runtime_api_client::body::Body; use serde::Deserialize; use std::{boxed::Box, fmt, sync::Arc}; use tokio::sync::Mutex; @@ -256,8 +260,8 @@ pub struct RuntimeDoneMetrics { /// underlying `Service` to process. pub(crate) async fn telemetry_wrapper( service: Arc>, - req: hyper::Request, -) -> Result, Box> + req: Request, +) -> Result, Box> where S: Service, Response = ()>, S::Error: Into> + fmt::Debug, @@ -265,24 +269,24 @@ where { trace!("Received telemetry request"); // Parse the request body as a Vec - let body = match hyper::body::to_bytes(req.into_body()).await { + let body = match req.into_body().collect().await { Ok(body) => body, Err(e) => { error!("Error reading telemetry request body: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; - let telemetry: Vec = match serde_json::from_slice(&body) { + let telemetry: Vec = match serde_json::from_slice(&body.to_bytes()) { Ok(telemetry) => telemetry, Err(e) => { error!("Error parsing telemetry: {}", e); return Ok(hyper::Response::builder() .status(hyper::StatusCode::BAD_REQUEST) - .body(hyper::Body::empty()) + .body(Body::empty()) .unwrap()); } }; @@ -295,7 +299,7 @@ where } } - Ok(hyper::Response::new(hyper::Body::empty())) + Ok(hyper::Response::new(Body::empty())) } #[cfg(test)] diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index fc93d88f..057134a6 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -23,21 +23,24 @@ apigw_websockets = [] alb = [] [dependencies] -base64 = "0.21" -bytes = "1.4" -futures = "0.3" -http = "0.2" -http-body = "0.4" -hyper = "0.14" +base64 = { workspace = true } +bytes = { workspace = true } +encoding_rs = "0.8" +futures = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } +mime = "0.3" +percent-encoding = "2.2" +pin-project-lite = { workspace = true } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" tokio-stream = "0.1.2" -mime = "0.3" -encoding_rs = "0.8" url = "2.2" -percent-encoding = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" @@ -46,6 +49,7 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index 313090c6..cfbdaec2 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -7,20 +7,24 @@ use lambda_runtime::Context; use crate::request::RequestContext; /// ALB/API gateway pre-parsed http query string parameters +#[derive(Clone)] pub(crate) struct QueryStringParameters(pub(crate) QueryMap); /// API gateway pre-extracted url path parameters /// /// These will always be empty for ALB requests +#[derive(Clone)] pub(crate) struct PathParameters(pub(crate) QueryMap); /// API gateway configured /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// /// These will always be empty for ALB requests +#[derive(Clone)] pub(crate) struct StageVariables(pub(crate) QueryMap); /// ALB/API gateway raw http path without any stage information +#[derive(Clone)] pub(crate) struct RawHttpPath(pub(crate) String); /// Extensions for [`lambda_http::Request`], `http::request::Parts`, and `http::Extensions` structs diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e77ec181..d26ef838 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -13,7 +13,7 @@ use http::header::CONTENT_ENCODING; use http::HeaderMap; use http::{header::CONTENT_TYPE, Response, StatusCode}; use http_body::Body as HttpBody; -use hyper::body::to_bytes; +use http_body_util::BodyExt; use mime::{Mime, CHARSET}; use serde::Serialize; use std::borrow::Cow; @@ -305,7 +305,15 @@ where B::Data: Send, B::Error: fmt::Debug, { - Box::pin(async move { Body::from(to_bytes(body).await.expect("unable to read bytes from body").to_vec()) }) + Box::pin(async move { + Body::from( + body.collect() + .await + .expect("unable to read bytes from body") + .to_bytes() + .to_vec(), + ) + }) } fn convert_to_text(body: B, content_type: &str) -> BodyFuture @@ -326,7 +334,7 @@ where // assumes utf-8 Box::pin(async move { - let bytes = to_bytes(body).await.expect("unable to read bytes from body"); + let bytes = body.collect().await.expect("unable to read bytes from body").to_bytes(); let (content, _, _) = encoding.decode(&bytes); match content { @@ -345,7 +353,7 @@ mod tests { header::{CONTENT_ENCODING, CONTENT_TYPE}, Response, StatusCode, }; - use hyper::Body as HyperBody; + use lambda_runtime_api_client::body::Body as HyperBody; use serde_json::{self, json}; const SVG_LOGO: &str = include_str!("../tests/data/svg_logo.svg"); diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index a59cf700..601e699b 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -63,19 +63,11 @@ where lambda_runtime::run(svc).await } +pin_project_lite::pin_project! { pub struct BodyStream { + #[pin] pub(crate) body: B, } - -impl BodyStream -where - B: Body + Unpin + Send + 'static, - B::Data: Into + Send, - B::Error: Into + Send + Debug, -{ - fn project(self: Pin<&mut Self>) -> Pin<&mut B> { - unsafe { self.map_unchecked_mut(|s| &mut s.body) } - } } impl Stream for BodyStream @@ -86,8 +78,14 @@ where { type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let body = self.project(); - body.poll_data(cx) + #[inline] + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match futures_util::ready!(self.as_mut().project().body.poll_frame(cx)?) { + Some(frame) => match frame.into_data() { + Ok(data) => Poll::Ready(Some(Ok(data))), + Err(_frame) => Poll::Ready(None), + }, + None => Poll::Ready(None), + } } } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index ae3e0f70..8868b145 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -4,7 +4,7 @@ version = "0.8.0" edition = "2021" authors = [ "David Calavera ", - "Harold Sun " + "Harold Sun ", ] description = "AWS Lambda Runtime interaction API" license = "Apache-2.0" @@ -14,7 +14,19 @@ keywords = ["AWS", "Lambda", "API"] readme = "README.md" [dependencies] -http = "0.2" -hyper = { version = "0.14.20", features = ["http1", "client", "stream", "tcp"] } -tower-service = "0.3" +bytes = { workspace = true } +futures-channel = { workspace = true } +futures-util = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true, features = ["http1", "client"] } +hyper-util = { workspace = true, features = [ + "client", + "client-legacy", + "http1", + "tokio", +] } +tower = { workspace = true, features = ["util"] } +tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs new file mode 100644 index 00000000..815de5f2 --- /dev/null +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -0,0 +1,110 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; + +use crate::body::{sender, watch}; +use bytes::Bytes; +use futures_channel::mpsc; +use futures_channel::oneshot; +use futures_util::{stream::FusedStream, Future, Stream}; +use http::HeaderMap; +use http_body::Body; +use http_body::Frame; +use http_body::SizeHint; +pub use sender::Sender; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub(crate) struct DecodedLength(u64); + +impl DecodedLength { + pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX); + pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1); + pub(crate) const ZERO: DecodedLength = DecodedLength(0); + + pub(crate) fn sub_if(&mut self, amt: u64) { + match *self { + DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (), + DecodedLength(ref mut known) => { + *known -= amt; + } + } + } + + /// Converts to an Option representing a Known or Unknown length. + pub(crate) fn into_opt(self) -> Option { + match self { + DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None, + DecodedLength(known) => Some(known), + } + } +} + +pub struct ChannelBody { + content_length: DecodedLength, + want_tx: watch::Sender, + data_rx: mpsc::Receiver>, + trailers_rx: oneshot::Receiver, +} + +pub fn channel() -> (Sender, ChannelBody) { + let (data_tx, data_rx) = mpsc::channel(0); + let (trailers_tx, trailers_rx) = oneshot::channel(); + + let (want_tx, want_rx) = watch::channel(sender::WANT_READY); + + let tx = Sender { + want_rx, + data_tx, + trailers_tx: Some(trailers_tx), + }; + let rx = ChannelBody { + content_length: DecodedLength::CHUNKED, + want_tx, + data_rx, + trailers_rx, + }; + + (tx, rx) +} + +impl Body for ChannelBody { + type Data = Bytes; + type Error = crate::Error; + + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + self.want_tx.send(sender::WANT_READY); + + if !self.data_rx.is_terminated() { + if let Some(chunk) = ready!(Pin::new(&mut self.data_rx).poll_next(cx)?) { + self.content_length.sub_if(chunk.len() as u64); + return Poll::Ready(Some(Ok(Frame::data(chunk)))); + } + } + + // check trailers after data is terminated + match ready!(Pin::new(&mut self.trailers_rx).poll(cx)) { + Ok(t) => Poll::Ready(Some(Ok(Frame::trailers(t)))), + Err(_) => Poll::Ready(None), + } + } + + fn is_end_stream(&self) -> bool { + self.content_length == DecodedLength::ZERO + } + + fn size_hint(&self) -> SizeHint { + let mut hint = SizeHint::default(); + + if let Some(content_length) = self.content_length.into_opt() { + hint.set_exact(content_length); + } + + hint + } +} diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs new file mode 100644 index 00000000..7e2d597c --- /dev/null +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -0,0 +1,143 @@ +//! HTTP body utilities. Extracted from Axum under MIT license. +//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE + +use crate::{BoxError, Error}; +use bytes::Bytes; +use futures_util::stream::Stream; +use http_body::{Body as _, Frame}; +use http_body_util::{BodyExt, Collected}; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use self::channel::Sender; + +macro_rules! ready { + ($e:expr) => { + match $e { + std::task::Poll::Ready(v) => v, + std::task::Poll::Pending => return std::task::Poll::Pending, + } + }; +} + +mod channel; +pub mod sender; +mod watch; + +type BoxBody = http_body_util::combinators::UnsyncBoxBody; + +fn boxed(body: B) -> BoxBody +where + B: http_body::Body + Send + 'static, + B::Error: Into, +{ + try_downcast(body).unwrap_or_else(|body| body.map_err(Error::new).boxed_unsync()) +} + +pub(crate) fn try_downcast(k: K) -> Result +where + T: 'static, + K: Send + 'static, +{ + let mut k = Some(k); + if let Some(k) = ::downcast_mut::>(&mut k) { + Ok(k.take().unwrap()) + } else { + Err(k.unwrap()) + } +} + +/// The body type used in axum requests and responses. +#[derive(Debug)] +pub struct Body(BoxBody); + +impl Body { + /// Create a new `Body` that wraps another [`http_body::Body`]. + pub fn new(body: B) -> Self + where + B: http_body::Body + Send + 'static, + B::Error: Into, + { + try_downcast(body).unwrap_or_else(|body| Self(boxed(body))) + } + + /// Create an empty body. + pub fn empty() -> Self { + Self::new(http_body_util::Empty::new()) + } + + /// Create a new `Body` stream with associated Sender half. + pub fn channel() -> (Sender, Body) { + let (sender, body) = channel::channel(); + (sender, Body::new(body)) + } + + /// Collect the body into `Bytes` + pub async fn collect(self) -> Result, Error> { + self.0.collect().await + } +} + +impl Default for Body { + fn default() -> Self { + Self::empty() + } +} + +macro_rules! body_from_impl { + ($ty:ty) => { + impl From<$ty> for Body { + fn from(buf: $ty) -> Self { + Self::new(http_body_util::Full::from(buf)) + } + } + }; +} + +body_from_impl!(&'static [u8]); +body_from_impl!(std::borrow::Cow<'static, [u8]>); +body_from_impl!(Vec); + +body_from_impl!(&'static str); +body_from_impl!(std::borrow::Cow<'static, str>); +body_from_impl!(String); + +body_from_impl!(Bytes); + +impl http_body::Body for Body { + type Data = Bytes; + type Error = Error; + + #[inline] + fn poll_frame( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>>> { + Pin::new(&mut self.0).poll_frame(cx) + } + + #[inline] + fn size_hint(&self) -> http_body::SizeHint { + self.0.size_hint() + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } +} + +impl Stream for Body { + type Item = Result; + + #[inline] + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + match futures_util::ready!(Pin::new(&mut self).poll_frame(cx)?) { + Some(frame) => match frame.into_data() { + Ok(data) => Poll::Ready(Some(Ok(data))), + Err(_frame) => Poll::Ready(None), + }, + None => Poll::Ready(None), + } + } +} diff --git a/lambda-runtime-api-client/src/body/sender.rs b/lambda-runtime-api-client/src/body/sender.rs new file mode 100644 index 00000000..0e008454 --- /dev/null +++ b/lambda-runtime-api-client/src/body/sender.rs @@ -0,0 +1,135 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +use crate::Error; +use std::task::{Context, Poll}; + +use bytes::Bytes; +use futures_channel::{mpsc, oneshot}; +use http::HeaderMap; + +use super::watch; + +type BodySender = mpsc::Sender>; +type TrailersSender = oneshot::Sender; + +pub(crate) const WANT_PENDING: usize = 1; +pub(crate) const WANT_READY: usize = 2; + +/// A sender half created through [`Body::channel()`]. +/// +/// Useful when wanting to stream chunks from another thread. +/// +/// ## Body Closing +/// +/// Note that the request body will always be closed normally when the sender is dropped (meaning +/// that the empty terminating chunk will be sent to the remote). If you desire to close the +/// connection with an incomplete response (e.g. in the case of an error during asynchronous +/// processing), call the [`Sender::abort()`] method to abort the body in an abnormal fashion. +/// +/// [`Body::channel()`]: struct.Body.html#method.channel +/// [`Sender::abort()`]: struct.Sender.html#method.abort +#[must_use = "Sender does nothing unless sent on"] +pub struct Sender { + pub(crate) want_rx: watch::Receiver, + pub(crate) data_tx: BodySender, + pub(crate) trailers_tx: Option, +} + +impl Sender { + /// Check to see if this `Sender` can send more data. + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + // Check if the receiver end has tried polling for the body yet + ready!(self.poll_want(cx)?); + self.data_tx + .poll_ready(cx) + .map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + fn poll_want(&mut self, cx: &mut Context<'_>) -> Poll> { + match self.want_rx.load(cx) { + WANT_READY => Poll::Ready(Ok(())), + WANT_PENDING => Poll::Pending, + watch::CLOSED => Poll::Ready(Err(Error::new(SenderError::ChannelClosed))), + unexpected => unreachable!("want_rx value: {}", unexpected), + } + } + + async fn ready(&mut self) -> Result<(), Error> { + futures_util::future::poll_fn(|cx| self.poll_ready(cx)).await + } + + /// Send data on data channel when it is ready. + #[allow(unused)] + pub async fn send_data(&mut self, chunk: Bytes) -> Result<(), Error> { + self.ready().await?; + self.data_tx + .try_send(Ok(chunk)) + .map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + /// Send trailers on trailers channel. + #[allow(unused)] + pub async fn send_trailers(&mut self, trailers: HeaderMap) -> Result<(), Error> { + let tx = match self.trailers_tx.take() { + Some(tx) => tx, + None => return Err(Error::new(SenderError::ChannelClosed)), + }; + tx.send(trailers).map_err(|_| Error::new(SenderError::ChannelClosed)) + } + + /// Try to send data on this channel. + /// + /// # Errors + /// + /// Returns `Err(Bytes)` if the channel could not (currently) accept + /// another `Bytes`. + /// + /// # Note + /// + /// This is mostly useful for when trying to send from some other thread + /// that doesn't have an async context. If in an async context, prefer + /// `send_data()` instead. + pub fn try_send_data(&mut self, chunk: Bytes) -> Result<(), Bytes> { + self.data_tx + .try_send(Ok(chunk)) + .map_err(|err| err.into_inner().expect("just sent Ok")) + } + + /// Send a `SenderError::BodyWriteAborted` error and terminate the stream. + #[allow(unused)] + pub fn abort(mut self) { + self.send_error(Error::new(SenderError::BodyWriteAborted)); + } + + /// Terminate the stream with an error. + pub fn send_error(&mut self, err: Error) { + let _ = self + .data_tx + // clone so the send works even if buffer is full + .clone() + .try_send(Err(err)); + } +} + +#[derive(Debug)] +enum SenderError { + ChannelClosed, + BodyWriteAborted, +} + +impl SenderError { + fn description(&self) -> &str { + match self { + SenderError::BodyWriteAborted => "user body write aborted", + SenderError::ChannelClosed => "channel closed", + } + } +} + +impl std::fmt::Display for SenderError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.description()) + } +} +impl std::error::Error for SenderError {} diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs new file mode 100644 index 00000000..ac0bd4ee --- /dev/null +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -0,0 +1,69 @@ +//! Body::channel utilities. Extracted from Hyper under MIT license. +//! https://github.com/hyperium/hyper/blob/master/LICENSE + +//! An SPSC broadcast channel. +//! +//! - The value can only be a `usize`. +//! - The consumer is only notified if the value is different. +//! - The value `0` is reserved for closed. + +use futures_util::task::AtomicWaker; +use std::sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, +}; +use std::task; + +type Value = usize; + +pub(crate) const CLOSED: usize = 0; + +pub(crate) fn channel(initial: Value) -> (Sender, Receiver) { + debug_assert!(initial != CLOSED, "watch::channel initial state of 0 is reserved"); + + let shared = Arc::new(Shared { + value: AtomicUsize::new(initial), + waker: AtomicWaker::new(), + }); + + (Sender { shared: shared.clone() }, Receiver { shared }) +} + +pub(crate) struct Sender { + shared: Arc, +} + +pub(crate) struct Receiver { + shared: Arc, +} + +struct Shared { + value: AtomicUsize, + waker: AtomicWaker, +} + +impl Sender { + pub(crate) fn send(&mut self, value: Value) { + if self.shared.value.swap(value, Ordering::SeqCst) != value { + self.shared.waker.wake(); + } + } +} + +impl Drop for Sender { + fn drop(&mut self) { + self.send(CLOSED); + } +} + +impl Receiver { + pub(crate) fn load(&mut self, cx: &mut task::Context<'_>) -> Value { + self.shared.waker.register(cx.waker()); + self.shared.value.load(Ordering::SeqCst) + } + + #[allow(unused)] + pub(crate) fn peek(&self) -> Value { + self.shared.value.load(Ordering::Relaxed) + } +} diff --git a/lambda-runtime-api-client/src/error.rs b/lambda-runtime-api-client/src/error.rs new file mode 100644 index 00000000..dbb87b64 --- /dev/null +++ b/lambda-runtime-api-client/src/error.rs @@ -0,0 +1,33 @@ +//! Extracted from Axum under MIT license. +//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +use std::{error::Error as StdError, fmt}; +pub use tower::BoxError; +/// Errors that can happen when using axum. +#[derive(Debug)] +pub struct Error { + inner: BoxError, +} + +impl Error { + /// Create a new `Error` from a boxable error. + pub fn new(error: impl Into) -> Self { + Self { inner: error.into() } + } + + /// Convert an `Error` back into the underlying boxed trait object. + pub fn into_inner(self) -> BoxError { + self.inner + } +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.inner.fmt(f) + } +} + +impl StdError for Error { + fn source(&self) -> Option<&(dyn StdError + 'static)> { + Some(&*self.inner) + } +} diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 4b082aba..15185f81 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -5,20 +5,18 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; -use hyper::{ - client::{connect::Connection, HttpConnector}, - Body, -}; +use hyper::body::Incoming; +use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; use std::{convert::TryInto, fmt::Debug}; -use tokio::io::{AsyncRead, AsyncWrite}; use tower_service::Service; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); const CUSTOM_USER_AGENT: Option<&str> = option_env!("LAMBDA_RUNTIME_USER_AGENT"); -/// Error type that lambdas may result in -pub type Error = Box; +mod error; +pub use error::*; +pub mod body; /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] @@ -26,7 +24,7 @@ pub struct Client { /// The runtime API URI pub base: Uri, /// The client that manages the API connections - pub client: hyper::Client, + pub client: hyper_util::client::legacy::Client, } impl Client { @@ -41,25 +39,24 @@ impl Client { impl Client where - C: hyper::client::connect::Connect + Sync + Send + Clone + 'static, + C: Connect + Sync + Send + Clone + 'static, { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. - pub async fn call(&self, req: Request) -> Result, Error> { + pub async fn call(&self, req: Request) -> Result, BoxError> { let req = self.set_origin(req)?; - let response = self.client.request(req).await?; - Ok(response) + self.client.request(req).await.map_err(Into::into) } /// Create a new client with a given base URI and HTTP connector. pub fn with(base: Uri, connector: C) -> Self { - let client = hyper::Client::builder() + let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .http1_max_buf_size(1024 * 1024) .build(connector); Self { base, client } } - fn set_origin(&self, req: Request) -> Result, Error> { + fn set_origin(&self, req: Request) -> Result, BoxError> { let (mut parts, body) = req.into_parts(); let (scheme, authority, base_path) = { let scheme = self.base.scheme().unwrap_or(&Scheme::HTTP); @@ -83,7 +80,7 @@ where } /// Builder implementation to construct any Runtime API clients. -pub struct ClientBuilder = hyper::client::HttpConnector> { +pub struct ClientBuilder = HttpConnector> { connector: C, uri: Option, } @@ -93,7 +90,7 @@ where C: Service + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { /// Create a new builder with a given HTTP connector. pub fn with_connector(self, connector: C2) -> ClientBuilder @@ -101,7 +98,7 @@ where C2: Service + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { ClientBuilder { connector, @@ -116,7 +113,10 @@ where } /// Create the new client to interact with the Runtime API. - pub fn build(self) -> Result, Error> { + pub fn build(self) -> Result, Error> + where + C: Connect + Sync + Send + Clone + 'static, + { let uri = match self.uri { Some(uri) => uri, None => { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 335b5482..8a579d99 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -18,30 +18,45 @@ default = ["simulated"] simulated = [] [dependencies] +async-stream = "0.3" +base64 = { workspace = true } +bytes = { workspace = true } +futures = { workspace = true } +http = { workspace = true } +http-body = { workspace = true } +http-body-util = { workspace = true } +http-serde = { workspace = true } +hyper = { workspace = true, features = [ + "http1", + "client", +] } +hyper-util = { workspace = true, features = [ + "client", + "client-legacy", + "http1", + "tokio", +] } +lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +serde = { version = "1", features = ["derive", "rc"] } +serde_json = "^1" +serde_path_to_error = "0.1.11" tokio = { version = "1.0", features = [ "macros", "io-util", "sync", "rt-multi-thread", ] } -# Hyper requires the `server` feature to work on nightly -hyper = { version = "0.14.20", features = [ - "http1", +tokio-stream = "0.1.2" +tower = { workspace = true, features = ["util"] } +tracing = { version = "0.1", features = ["log"] } + +[dev-dependencies] +hyper-util = { workspace = true, features = [ "client", - "stream", + "client-legacy", + "http1", "server", + "server-auto", + "tokio", ] } -futures = "0.3" -serde = { version = "1", features = ["derive", "rc"] } -serde_json = "^1" -bytes = "1.0" -http = "0.2" -async-stream = "0.3" -tracing = { version = "0.1.37", features = ["log"] } -tower = { version = "0.4", features = ["util"] } -tokio-stream = "0.1.2" -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } -serde_path_to_error = "0.1.11" -http-serde = "1.1.3" -base64 = "0.21.0" -http-body = "0.4" +pin-project-lite = { workspace = true } \ No newline at end of file diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 49add76c..10dfce92 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -9,22 +9,18 @@ //! and runs the Lambda runtime. use bytes::Bytes; use futures::FutureExt; -use hyper::{ - client::{connect::Connection, HttpConnector}, - http::Request, - Body, -}; -use lambda_runtime_api_client::Client; +use http_body_util::BodyExt; +use hyper::{body::Incoming, http::Request}; +use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; +use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ env, fmt::{self, Debug, Display}, future::Future, - marker::PhantomData, panic, sync::Arc, }; -use tokio::io::{AsyncRead, AsyncWrite}; use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; @@ -34,6 +30,8 @@ mod deserializer; mod requests; #[cfg(test)] mod simulated; +/// Utilities for Lambda Streaming functions. +pub mod streaming; /// Types available to a Lambda function. mod types; @@ -43,7 +41,7 @@ pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, Me use types::invoke_request_id; /// Error type that lambdas may result in -pub type Error = lambda_runtime_api_client::Error; +pub type Error = lambda_runtime_api_client::BoxError; /// Configuration derived from environment variables. #[derive(Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize)] @@ -94,16 +92,16 @@ struct Runtime = HttpConnector> { impl Runtime where - C: Service + Clone + Send + Sync + Unpin + 'static, + C: Service + Connect + Clone + Send + Sync + Unpin + 'static, C::Future: Unpin + Send, C::Error: Into>, - C::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + C::Response: Connection + Unpin + Send + 'static, { async fn run( &self, - incoming: impl Stream, Error>> + Send, + incoming: impl Stream, Error>> + Send, mut handler: F, - ) -> Result<(), Error> + ) -> Result<(), BoxError> where F: Service>, F::Future: Future>, @@ -136,7 +134,7 @@ where // Group the handling in one future and instrument it with the span async { - let body = hyper::body::to_bytes(body).await?; + let body = body.collect().await?.to_bytes(); trace!("response body - {}", std::str::from_utf8(&body)?); #[cfg(debug_assertions)] @@ -169,13 +167,7 @@ where Ok(response) => match response { Ok(response) => { trace!("Ok response from handler (run loop)"); - EventCompletionRequest { - request_id, - body: response, - _unused_b: PhantomData, - _unused_s: PhantomData, - } - .into_req() + EventCompletionRequest::new(request_id, response).into_req() } Err(err) => build_event_error_request(request_id, err), }, @@ -204,12 +196,12 @@ where } } -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ +fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ where - C: Service + Clone + Send + Sync + Unpin + 'static, + C: Service + Connect + Clone + Send + Sync + Unpin + 'static, >::Future: Unpin + Send, >::Error: Into>, - >::Response: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, + >::Response: Connection + Unpin + Send + 'static, { async_stream::stream! { loop { @@ -293,20 +285,23 @@ mod endpoint_tests { }; use futures::future::BoxFuture; use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; - use hyper::{server::conn::Http, service::service_fn, Body}; - use lambda_runtime_api_client::Client; + use hyper::body::Incoming; + use hyper::rt::{Read, Write}; + use hyper::service::service_fn; + + use hyper_util::server::conn::auto::Builder; + use lambda_runtime_api_client::{body::Body, Client}; use serde_json::json; use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, marker::PhantomData, sync::Arc}; + use std::{convert::TryFrom, env, sync::Arc}; use tokio::{ - io::{self, AsyncRead, AsyncWrite}, - select, + io, select, sync::{self, oneshot}, }; use tokio_stream::StreamExt; #[cfg(test)] - async fn next_event(req: &Request) -> Result, Error> { + async fn next_event(req: &Request) -> Result, Error> { let path = "/2018-06-01/runtime/invocation/next"; assert_eq!(req.method(), Method::GET); assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); @@ -323,7 +318,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn complete_event(req: &Request, id: &str) -> Result, Error> { + async fn complete_event(req: &Request, id: &str) -> Result, Error> { assert_eq!(Method::POST, req.method()); let rsp = Response::builder() .status(StatusCode::ACCEPTED) @@ -337,7 +332,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn event_err(req: &Request, id: &str) -> Result, Error> { + async fn event_err(req: &Request, id: &str) -> Result, Error> { let expected = format!("/2018-06-01/runtime/invocation/{id}/error"); assert_eq!(expected, req.uri().path()); @@ -351,7 +346,7 @@ mod endpoint_tests { } #[cfg(test)] - async fn handle_incoming(req: Request) -> Result, Error> { + async fn handle_incoming(req: Request) -> Result, Error> { let path: Vec<&str> = req .uri() .path_and_query() @@ -369,11 +364,14 @@ mod endpoint_tests { } #[cfg(test)] - async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), hyper::Error> + async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), Error> where - I: AsyncRead + AsyncWrite + Unpin + 'static, + I: Read + Write + Unpin + 'static, { - let conn = Http::new().serve_connection(io, service_fn(handle_incoming)); + use hyper_util::rt::TokioExecutor; + + let builder = Builder::new(TokioExecutor::new()); + let conn = builder.serve_connection(io, service_fn(handle_incoming)); select! { _ = rx => { Ok(()) @@ -396,7 +394,9 @@ mod endpoint_tests { let (tx, rx) = sync::oneshot::channel(); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -425,18 +425,15 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; let client = Client::with(base, conn); - let req = EventCompletionRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - body: "done", - _unused_b: PhantomData::<&str>, - _unused_s: PhantomData::, - }; + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "done"); let req = req.into_req()?; let rsp = client.call(req).await?; @@ -458,7 +455,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -491,7 +490,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; @@ -554,7 +555,9 @@ mod endpoint_tests { let base = Uri::from_static("http://localhost:9001"); let server = tokio::spawn(async { - handle(server, rx).await.expect("Unable to handle request"); + handle(DuplexStreamWrapper::new(server), rx) + .await + .expect("Unable to handle request"); }); let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 8e72fc2d..c9274cf4 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -3,8 +3,7 @@ use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; use http::{Method, Request, Response, Uri}; -use hyper::Body; -use lambda_runtime_api_client::build_request; +use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; use std::fmt::Debug; use std::marker::PhantomData; @@ -28,7 +27,7 @@ impl IntoRequest for NextEventRequest { let req = build_request() .method(Method::GET) .uri(Uri::from_static("/2018-06-01/runtime/invocation/next")) - .body(Body::empty())?; + .body(Default::default())?; Ok(req) } } @@ -49,6 +48,7 @@ pub struct NextEventResponse<'a> { impl<'a> IntoResponse for NextEventResponse<'a> { fn into_rsp(self) -> Result, Error> { + // let body: BoxyBody< = BoxBody::new(); let rsp = Response::builder() .header("lambda-runtime-aws-request-id", self.request_id) .header("lambda-runtime-deadline-ms", self.deadline) @@ -85,6 +85,25 @@ where pub(crate) _unused_s: PhantomData, } +impl<'a, R, B, D, E, S> EventCompletionRequest<'a, R, B, S, D, E> +where + R: IntoFunctionResponse, + B: Serialize, + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + /// Initialize a new EventCompletionRequest + pub(crate) fn new(request_id: &'a str, body: R) -> EventCompletionRequest<'a, R, B, S, D, E> { + EventCompletionRequest { + request_id, + body, + _unused_b: PhantomData::, + _unused_s: PhantomData::, + } + } +} + impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> where R: IntoFunctionResponse, @@ -157,12 +176,7 @@ where #[test] fn test_event_completion_request() { - let req = EventCompletionRequest { - request_id: "id", - body: "hello, world!", - _unused_b: PhantomData::<&str>, - _unused_s: PhantomData::, - }; + let req = EventCompletionRequest::new("id", "hello, world!"); let req = req.into_req().unwrap(); let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/response"); assert_eq!(req.method(), Method::POST); diff --git a/lambda-runtime/src/simulated.rs b/lambda-runtime/src/simulated.rs index f6a06bca..018664fe 100644 --- a/lambda-runtime/src/simulated.rs +++ b/lambda-runtime/src/simulated.rs @@ -1,14 +1,15 @@ use http::Uri; -use hyper::client::connect::Connection; +use hyper::rt::{Read, Write}; +use hyper_util::client::legacy::connect::{Connected, Connection}; +use pin_project_lite::pin_project; use std::{ collections::HashMap, future::Future, - io::Result as IoResult, pin::Pin, sync::{Arc, Mutex}, task::{Context, Poll}, }; -use tokio::io::{AsyncRead, AsyncWrite, DuplexStream, ReadBuf}; +use tokio::io::DuplexStream; use crate::Error; @@ -17,11 +18,16 @@ pub struct Connector { inner: Arc>>, } -pub struct DuplexStreamWrapper(DuplexStream); +pin_project! { +pub struct DuplexStreamWrapper { + #[pin] + inner: DuplexStream, +} +} impl DuplexStreamWrapper { - pub(crate) fn new(stream: DuplexStream) -> DuplexStreamWrapper { - DuplexStreamWrapper(stream) + pub(crate) fn new(inner: DuplexStream) -> DuplexStreamWrapper { + DuplexStreamWrapper { inner } } } @@ -53,16 +59,12 @@ impl Connector { } } -impl hyper::service::Service for Connector { +impl tower::Service for Connector { type Response = DuplexStreamWrapper; type Error = crate::Error; #[allow(clippy::type_complexity)] type Future = Pin> + Send>>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - fn call(&mut self, uri: Uri) -> Self::Future { let res = match self.inner.lock() { Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), @@ -71,30 +73,61 @@ impl hyper::service::Service for Connector { }; Box::pin(async move { res }) } + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } } impl Connection for DuplexStreamWrapper { - fn connected(&self) -> hyper::client::connect::Connected { - hyper::client::connect::Connected::new() + fn connected(&self) -> Connected { + Connected::new() } } -impl AsyncRead for DuplexStreamWrapper { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>) -> Poll> { - Pin::new(&mut self.0).poll_read(cx, buf) +impl Read for DuplexStreamWrapper { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + mut buf: hyper::rt::ReadBufCursor<'_>, + ) -> Poll> { + let n = unsafe { + let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut()); + match tokio::io::AsyncRead::poll_read(self.project().inner, cx, &mut tbuf) { + Poll::Ready(Ok(())) => tbuf.filled().len(), + other => return other, + } + }; + + unsafe { + buf.advance(n); + } + Poll::Ready(Ok(())) } } -impl AsyncWrite for DuplexStreamWrapper { - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - Pin::new(&mut self.0).poll_write(cx, buf) +impl Write for DuplexStreamWrapper { + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + tokio::io::AsyncWrite::poll_write(self.project().inner, cx, buf) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_flush(self.project().inner, cx) + } + + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_flush(cx) + fn is_write_vectored(&self) -> bool { + tokio::io::AsyncWrite::is_write_vectored(&self.inner) } - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.0).poll_shutdown(cx) + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + tokio::io::AsyncWrite::poll_write_vectored(self.project().inner, cx, bufs) } } diff --git a/lambda-runtime/src/streaming.rs b/lambda-runtime/src/streaming.rs new file mode 100644 index 00000000..4f0c8083 --- /dev/null +++ b/lambda-runtime/src/streaming.rs @@ -0,0 +1,35 @@ +pub use lambda_runtime_api_client::body::{sender::Sender, Body}; + +pub use crate::types::StreamResponse as Response; + +/// Create a new `Body` stream with associated Sender half. +/// +/// Examples +/// +/// ``` +/// use lambda_runtime::{ +/// streaming::{channel, Body, Response}, +/// Error, LambdaEvent, +/// }; +/// use std::{thread, time::Duration}; +/// +/// async fn func(_event: LambdaEvent) -> Result, Error> { +/// let messages = vec!["Hello", "world", "from", "Lambda!"]; +/// +/// let (mut tx, rx) = channel(); +/// +/// tokio::spawn(async move { +/// for message in messages.iter() { +/// tx.send_data((message.to_string() + "\n").into()).await.unwrap(); +/// thread::sleep(Duration::from_millis(500)); +/// } +/// }); +/// +/// Ok(Response::from(rx)) +/// } +/// ``` +#[allow(unused)] +#[inline] +pub fn channel() -> (Sender, Body) { + Body::channel() +} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 8b70ce80..f2a36073 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -2,6 +2,7 @@ use crate::{Error, RefConfig}; use base64::prelude::*; use bytes::Bytes; use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; +use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -251,11 +252,11 @@ impl IntoFunctionResponse for FunctionResponse { } } -impl IntoFunctionResponse for B +impl IntoFunctionResponse for B where B: Serialize, { - fn into_response(self) -> FunctionResponse { + fn into_response(self) -> FunctionResponse { FunctionResponse::BufferedResponse(self) } } @@ -271,6 +272,20 @@ where } } +impl From for StreamResponse +where + S: Stream> + Unpin + Send + 'static, + D: Into + Send, + E: Into + Send + Debug, +{ + fn from(value: S) -> Self { + StreamResponse { + metadata_prelude: Default::default(), + stream: value, + } + } +} + #[cfg(test)] mod test { use super::*; From 953b2d2dae2e2e16c3143b992bc6640e35e6625d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 18 Dec 2023 11:37:24 -0800 Subject: [PATCH 054/211] Fix EventBridge event structures. (#755) According to the docs, the detail is always a JSON object: https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html Signed-off-by: David Calavera --- lambda-events/src/event/eventbridge/mod.rs | 59 +++++-------------- .../example-eventbridge-event-obj.json | 18 +++--- .../fixtures/example-eventbridge-event.json | 13 ---- 3 files changed, 24 insertions(+), 66 deletions(-) delete mode 100644 lambda-events/src/fixtures/example-eventbridge-event.json diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 7809f1e2..ed9bf447 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,34 +1,15 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; -use serde_with::serde_as; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "kebab-case")] -pub struct EventBridgeEvent { - #[serde(default)] - pub version: Option, - #[serde(default)] - pub id: Option, - pub detail_type: String, - pub source: String, - #[serde(default)] - pub account: Option, - #[serde(default)] - pub time: Option>, - #[serde(default)] - pub region: Option, - #[serde(default)] - pub resources: Option>, - #[serde(default)] - pub detail: Option, -} - -#[serde_with::serde_as] +/// Parse EventBridge events. +/// Deserialize the event detail into a structure that's `DeserializeOwned`. +/// +/// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] -pub struct EventBridgeEventObj { +pub struct EventBridgeEvent { #[serde(default)] pub version: Option, #[serde(default)] @@ -43,7 +24,6 @@ pub struct EventBridgeEventObj { pub region: Option, #[serde(default)] pub resources: Option>, - #[serde_as(as = "serde_with::json::JsonString")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub detail: T, } @@ -53,35 +33,24 @@ pub struct EventBridgeEventObj { mod test { use super::*; - use serde_json; - #[test] fn example_eventbridge_obj_event() { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] - struct CustomStruct { - a: String, - b: String, + #[serde(rename_all = "kebab-case")] + struct Ec2StateChange { + instance_id: String, + state: String, } + // Example from https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/monitoring-instance-state-changes.html let data = include_bytes!("../../fixtures/example-eventbridge-event-obj.json"); - let parsed: EventBridgeEventObj = serde_json::from_slice(data).unwrap(); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); - assert_eq!(parsed.detail.a, "123"); - assert_eq!(parsed.detail.b, "456"); - - let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: EventBridgeEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); - assert_eq!(parsed, reparsed); - } - - #[test] - fn example_eventbridge_event() { - let data = include_bytes!("../../fixtures/example-eventbridge-event.json"); - let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); - assert_eq!(parsed.detail, Some(String::from("String Message"))); + assert_eq!("i-abcd1111", parsed.detail.instance_id); + assert_eq!("pending", parsed.detail.state); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } diff --git a/lambda-events/src/fixtures/example-eventbridge-event-obj.json b/lambda-events/src/fixtures/example-eventbridge-event-obj.json index 97c5e0ae..e9b26968 100644 --- a/lambda-events/src/fixtures/example-eventbridge-event-obj.json +++ b/lambda-events/src/fixtures/example-eventbridge-event-obj.json @@ -1,13 +1,15 @@ { - "version": "0", - "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", + "id": "7bf73129-1428-4cd3-a780-95db273d1602", "detail-type": "EC2 Instance State-change Notification", "source": "aws.ec2", - "account": "111122223333", - "time": "2017-12-22T18:43:48Z", - "region": "us-west-1", + "account": "123456789012", + "time": "2021-11-11T21:29:54Z", + "region": "us-east-1", "resources": [ - "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" + "arn:aws:ec2:us-east-1:123456789012:instance/i-abcd1111" ], - "detail": "{\"a\":\"123\",\"b\":\"456\"}" -} + "detail": { + "instance-id": "i-abcd1111", + "state": "pending" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-eventbridge-event.json b/lambda-events/src/fixtures/example-eventbridge-event.json deleted file mode 100644 index 793ca8dc..00000000 --- a/lambda-events/src/fixtures/example-eventbridge-event.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "version": "0", - "id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718", - "detail-type": "EC2 Instance State-change Notification", - "source": "aws.ec2", - "account": "111122223333", - "time": "2017-12-22T18:43:48Z", - "region": "us-west-1", - "resources": [ - "arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0" - ], - "detail": "String Message" -} From ead7f3754258b00d929444762dc44db7dba3de92 Mon Sep 17 00:00:00 2001 From: Titus <8200809+nismotie@users.noreply.github.com> Date: Wed, 20 Dec 2023 17:02:23 +0000 Subject: [PATCH 055/211] Implement Serialize for lambda telemetry (#759) * Add serialize testing mod and macro * Use and derive serialize * Skip serialize if None on Options * Add unit tests for serialization --- lambda-extension/src/telemetry.rs | 231 ++++++++++++++++++++++++++++-- 1 file changed, 216 insertions(+), 15 deletions(-) diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index 1e83ee8e..cfb4dde2 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -3,14 +3,14 @@ use http::{Request, Response}; use http_body_util::BodyExt; use hyper::body::Incoming; use lambda_runtime_api_client::body::Body; -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::{boxed::Box, fmt, sync::Arc}; use tokio::sync::Mutex; use tower::Service; use tracing::{error, trace}; /// Payload received from the Telemetry API -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct LambdaTelemetry { /// Time when the telemetry was generated pub time: DateTime, @@ -20,7 +20,7 @@ pub struct LambdaTelemetry { } /// Record in a LambdaTelemetry entry -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(tag = "type", content = "record", rename_all = "lowercase")] pub enum LambdaTelemetryRecord { /// Function log records @@ -37,8 +37,10 @@ pub enum LambdaTelemetryRecord { /// Phase of initialisation phase: InitPhase, /// Lambda runtime version + #[serde(skip_serializing_if = "Option::is_none")] runtime_version: Option, /// Lambda runtime version ARN + #[serde(skip_serializing_if = "Option::is_none")] runtime_version_arn: Option, }, /// Platform init runtime done record @@ -47,10 +49,12 @@ pub enum LambdaTelemetryRecord { /// Type of initialization initialization_type: InitType, /// Phase of initialisation + #[serde(skip_serializing_if = "Option::is_none")] phase: Option, /// Status of initalization status: Status, /// When the status = failure, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Spans #[serde(default)] @@ -75,8 +79,10 @@ pub enum LambdaTelemetryRecord { /// Request identifier request_id: String, /// Version of the Lambda function + #[serde(skip_serializing_if = "Option::is_none")] version: Option, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, /// Record marking the completion of an invocation @@ -87,13 +93,16 @@ pub enum LambdaTelemetryRecord { /// Status of the invocation status: Status, /// When unsuccessful, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Metrics corresponding to the runtime + #[serde(skip_serializing_if = "Option::is_none")] metrics: Option, /// Spans #[serde(default)] spans: Vec, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, /// Platfor report record @@ -104,6 +113,7 @@ pub enum LambdaTelemetryRecord { /// Status of the invocation status: Status, /// When unsuccessful, the error_type describes what kind of error occurred + #[serde(skip_serializing_if = "Option::is_none")] error_type: Option, /// Metrics metrics: ReportMetrics, @@ -111,6 +121,7 @@ pub enum LambdaTelemetryRecord { #[serde(default)] spans: Vec, /// Trace Context + #[serde(skip_serializing_if = "Option::is_none")] tracing: Option, }, @@ -147,7 +158,7 @@ pub enum LambdaTelemetryRecord { } /// Type of Initialization -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum InitType { /// Initialised on demand @@ -159,7 +170,7 @@ pub enum InitType { } /// Phase in which initialization occurs -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum InitPhase { /// Initialization phase @@ -169,7 +180,7 @@ pub enum InitPhase { } /// Status of invocation/initialization -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "kebab-case")] pub enum Status { /// Success @@ -183,7 +194,7 @@ pub enum Status { } /// Span -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct Span { /// Duration of the span @@ -195,7 +206,7 @@ pub struct Span { } /// Tracing Context -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct TraceContext { /// Span ID @@ -207,7 +218,7 @@ pub struct TraceContext { } /// Type of tracing -#[derive(Clone, Debug, Deserialize, Eq, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)] pub enum TracingType { /// Amazon trace type #[serde(rename = "X-Amzn-Trace-Id")] @@ -215,7 +226,7 @@ pub enum TracingType { } ///Init report metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct InitReportMetrics { /// Duration of initialization @@ -223,7 +234,7 @@ pub struct InitReportMetrics { } /// Report metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct ReportMetrics { /// Duration in milliseconds @@ -237,15 +248,15 @@ pub struct ReportMetrics { #[serde(rename = "maxMemoryUsedMB")] pub max_memory_used_mb: u64, /// Init duration in case of a cold start - #[serde(default = "Option::default")] + #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")] pub init_duration_ms: Option, /// Restore duration in milliseconds - #[serde(default = "Option::default")] + #[serde(default = "Option::default", skip_serializing_if = "Option::is_none")] pub restore_duration_ms: Option, } /// Runtime done metrics -#[derive(Clone, Debug, Deserialize, PartialEq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct RuntimeDoneMetrics { /// Duration in milliseconds @@ -303,7 +314,7 @@ where } #[cfg(test)] -mod tests { +mod deserialization_tests { use super::*; use chrono::{Duration, TimeZone}; @@ -459,3 +470,193 @@ mod tests { ), } } + +#[cfg(test)] +mod serialization_tests { + use chrono::{Duration, TimeZone}; + + use super::*; + macro_rules! serialize_tests { + ($($name:ident: $value:expr,)*) => { + $( + #[test] + fn $name() { + let (input, expected) = $value; + let actual = serde_json::to_string(&input).expect("unable to serialize"); + println!("Input: {:?}\n", input); + println!("Expected:\n {:?}\n", expected); + println!("Actual:\n {:?}\n", actual); + + assert!(actual == expected); + } + )* + } + } + + serialize_tests! { + // function + function: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::Function("hello world".to_string()), + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"function","record":"hello world"}"#, + ), + // extension + extension: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::Extension("hello world".to_string()), + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"extension","record":"hello world"}"#, + ), + //platform.Start + platform_start: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformStart { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + version: Some("$LATEST".to_string()), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + } + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.start","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","version":"$LATEST","tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.initStart + platform_init_start: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitStart { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + runtime_version: None, + runtime_version_arn: None, + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initStart","record":{"initializationType":"on-demand","phase":"init"}}"#, + ), + // platform.runtimeDone + platform_runtime_done: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformRuntimeDone { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: Some(RuntimeDoneMetrics { + duration_ms: 2599.0, + produced_bytes: Some(8), + }), + spans: vec!( + Span { + name:"responseLatency".to_string(), + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) + .unwrap() + .checked_add_signed(Duration::milliseconds(165)) + .unwrap(), + duration_ms: 2598.0 + }, + Span { + name:"responseDuration".to_string(), + start: Utc + .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) + .unwrap() + .checked_add_signed(Duration::milliseconds(763)) + .unwrap(), + duration_ms: 0.0 + }, + ), + tracing: Some(TraceContext{ + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.runtimeDone","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.0,"producedBytes":8},"spans":[{"durationMs":2598.0,"name":"responseLatency","start":"2022-10-21T14:05:03.165Z"},{"durationMs":0.0,"name":"responseDuration","start":"2022-10-21T14:05:05.763Z"}],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.report + platform_report: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformReport { + request_id: "459921b5-681c-4a96-beb0-81e0aa586026".to_string(), + status: Status::Success, + error_type: None, + metrics: ReportMetrics { + duration_ms: 2599.4, + billed_duration_ms: 2600, + memory_size_mb:128, + max_memory_used_mb:94, + init_duration_ms: Some(549.04), + restore_duration_ms: None, + }, + spans: Vec::new(), + tracing: Some(TraceContext { + span_id: Some("24cd7d670fa455f0".to_string()), + r#type: TracingType::AmznTraceId, + value: "Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1".to_string(), + }), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.report","record":{"requestId":"459921b5-681c-4a96-beb0-81e0aa586026","status":"success","metrics":{"durationMs":2599.4,"billedDurationMs":2600,"memorySizeMB":128,"maxMemoryUsedMB":94,"initDurationMs":549.04},"spans":[],"tracing":{"spanId":"24cd7d670fa455f0","type":"X-Amzn-Trace-Id","value":"Root=1-6352a70e-1e2c502e358361800241fd45;Parent=35465b3a9e2f7c6a;Sampled=1"}}}"#, + ), + // platform.telemetrySubscription + platform_telemetry_subscription: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformTelemetrySubscription { + name: "my-extension".to_string(), + state: "Subscribed".to_string(), + types: vec!("platform".to_string(), "function".to_string()), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.telemetrySubscription","record":{"name":"my-extension","state":"Subscribed","types":["platform","function"]}}"#, + ), + // platform.initRuntimeDone + platform_init_runtime_done: ( + LambdaTelemetry{ + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitRuntimeDone { + initialization_type: InitType::OnDemand, + status: Status::Success, + phase: None, + error_type: None, + spans: Vec::new(), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initRuntimeDone","record":{"initializationType":"on-demand","status":"success","spans":[]}}"#, + ), + // platform.extension + platform_extension: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformExtension { + name: "my-extension".to_string(), + state: "Ready".to_string(), + events: vec!("SHUTDOWN".to_string(), "INVOKE".to_string()), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.extension","record":{"name":"my-extension","state":"Ready","events":["SHUTDOWN","INVOKE"]}}"#, + ), + // platform.initReport + platform_init_report: ( + LambdaTelemetry { + time: Utc.with_ymd_and_hms(2023, 11, 28, 12, 0, 9).unwrap(), + record: LambdaTelemetryRecord::PlatformInitReport { + initialization_type: InitType::OnDemand, + phase: InitPhase::Init, + metrics: InitReportMetrics { duration_ms: 500.0 }, + spans: Vec::new(), + }, + }, + r#"{"time":"2023-11-28T12:00:09Z","type":"platform.initReport","record":{"initializationType":"on-demand","phase":"init","metrics":{"durationMs":500.0},"spans":[]}}"#, + ), + + } +} From ab3b809d63fad05369596d17ad4fc449f7035e5e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 20 Dec 2023 11:09:55 -0800 Subject: [PATCH 056/211] Release new versions compatible with Hyper 1 (#756) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 8 ++++---- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 29d4e191..244738e9 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.12.1" +version = "0.13.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 667b866f..ba0898a3 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.8.2" +version = "0.9.0" edition = "2021" authors = [ "David Calavera ", @@ -21,7 +21,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 057134a6..c3ec425e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.8.3" +version = "0.9.0" authors = [ "David Calavera ", "Harold Sun ", @@ -32,7 +32,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { path = "../lambda-runtime", version = "0.8.3" } +lambda_runtime = { version = "0.9", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -44,12 +44,12 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.12.0" +version = "0.13" default-features = false features = ["alb", "apigw"] [dev-dependencies] -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 8868b145..12b26043 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 8a579d99..f3c8e053 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.8.3" +version = "0.9.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.8", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" From a8f860ff6704227e21ad28dc800112a7aa3803f5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 26 Dec 2023 14:16:18 -0800 Subject: [PATCH 057/211] Cloudwatch alarms (#760) * Add CloudWatch Alarm events CloudWatch has added the ability to trigger Lambda functions base on alarms. See annoucement here: https://aws.amazon.com/about-aws/whats-new/2023/12/amazon-cloudwatch-alarms-lambda-change-action/ Signed-off-by: David Calavera * Remove unnecessary import from tests. Signed-off-by: David Calavera * Update events section in the readme. Signed-off-by: David Calavera * Remove empty lines. Signed-off-by: David Calavera * Implement custom serde interfaces with known reason data. Signed-off-by: David Calavera * Add more docs Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- Makefile | 1 + README.md | 2 +- lambda-events/Cargo.toml | 2 + lambda-events/src/custom_serde/mod.rs | 1 - lambda-events/src/encodings/http.rs | 1 - lambda-events/src/encodings/time.rs | 1 - lambda-events/src/event/activemq/mod.rs | 2 - lambda-events/src/event/alb/mod.rs | 2 - lambda-events/src/event/apigw/mod.rs | 2 - lambda-events/src/event/appsync/mod.rs | 2 - lambda-events/src/event/autoscaling/mod.rs | 2 - lambda-events/src/event/clientvpn/mod.rs | 2 - .../src/event/cloudwatch_alarms/mod.rs | 301 ++++++++++++++++++ lambda-events/src/event/code_commit/mod.rs | 2 - lambda-events/src/event/codebuild/mod.rs | 2 - lambda-events/src/event/codedeploy/mod.rs | 2 - .../src/event/codepipeline_cloudwatch/mod.rs | 2 - .../src/event/codepipeline_job/mod.rs | 2 - lambda-events/src/event/cognito/mod.rs | 2 - lambda-events/src/event/config/mod.rs | 2 - lambda-events/src/event/connect/mod.rs | 2 - lambda-events/src/event/dynamodb/mod.rs | 2 - lambda-events/src/event/ecr_scan/mod.rs | 2 - lambda-events/src/event/firehose/mod.rs | 2 - lambda-events/src/event/iot/mod.rs | 2 - lambda-events/src/event/iot_1_click/mod.rs | 1 - lambda-events/src/event/iot_button/mod.rs | 2 - lambda-events/src/event/kafka/mod.rs | 2 - lambda-events/src/event/kinesis/event.rs | 2 - lambda-events/src/event/lex/mod.rs | 2 - lambda-events/src/event/mod.rs | 6 +- lambda-events/src/event/rabbitmq/mod.rs | 2 - lambda-events/src/event/s3/event.rs | 2 - lambda-events/src/event/s3/object_lambda.rs | 2 - lambda-events/src/event/ses/mod.rs | 2 - lambda-events/src/event/sns/mod.rs | 2 - lambda-events/src/event/sqs/mod.rs | 2 - .../example-cloudwatch-alarm-composite.json | 30 ++ .../example-cloudwatch-alarm-metric.json | 42 +++ lambda-events/src/lib.rs | 6 +- lambda-events/src/time_window.rs | 2 - 41 files changed, 387 insertions(+), 65 deletions(-) create mode 100644 lambda-events/src/event/cloudwatch_alarms/mod.rs create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json diff --git a/Makefile b/Makefile index 049be8f8..f66ee1fd 100644 --- a/Makefile +++ b/Makefile @@ -72,6 +72,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features bedrock_agent_runtime cargo test --package aws_lambda_events --no-default-features --features chime_bot cargo test --package aws_lambda_events --no-default-features --features clientvpn + cargo test --package aws_lambda_events --no-default-features --features cloudwatch_alarms cargo test --package aws_lambda_events --no-default-features --features cloudwatch_events cargo test --package aws_lambda_events --no-default-features --features cloudwatch_logs cargo test --package aws_lambda_events --no-default-features --features code_commit diff --git a/README.md b/README.md index c586e657..ef43ba74 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt ## AWS event objects -This project does not currently include Lambda event struct definitions. Instead, the community-maintained [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events) crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. +This project includes Lambda event struct definitions, [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events). This crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. ### Custom event objects diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 244738e9..46ab7c11 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -47,6 +47,7 @@ default = [ "chime_bot", "clientvpn", "cloudformation", + "cloudwatch_alarms", "cloudwatch_events", "cloudwatch_logs", "code_commit", @@ -90,6 +91,7 @@ bedrock_agent_runtime = [] chime_bot = ["chrono"] clientvpn = [] cloudformation = [] +cloudwatch_alarms = ["chrono"] cloudwatch_events = ["chrono"] cloudwatch_logs = ["flate2"] code_commit = ["chrono"] diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 65f0f89a..82723c3f 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -96,7 +96,6 @@ where mod test { use super::*; use serde::{Deserialize, Serialize}; - use serde_json; #[test] fn test_deserialize_base64() { diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 1cb10c81..e013a698 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -246,7 +246,6 @@ impl HttpBody for Body { #[cfg(test)] mod tests { use super::*; - use serde_json; use std::collections::HashMap; #[test] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index a550b7b0..203aff75 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -215,7 +215,6 @@ where mod test { use super::*; use chrono::TimeZone; - use serde_json; #[test] fn test_deserialize_milliseconds() { diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 9469ece4..89cfda1c 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -54,8 +54,6 @@ pub struct ActiveMqDestination { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "activemq")] fn example_activemq_event() { diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 259dce23..a3f96d88 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -69,8 +69,6 @@ pub struct AlbTargetGroupResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "alb")] fn example_alb_lambda_target_request_headers_only() { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index fcc5ed0c..36512f9c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -769,8 +769,6 @@ fn default_http_method() -> Method { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "apigw")] fn example_apigw_custom_auth_request_type_request() { diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 120fc9e3..4035f181 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -122,8 +122,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "appsync")] fn example_appsync_identity_cognito() { diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index cc003daf..707b828a 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -48,8 +48,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "autoscaling")] fn example_autoscaling_event_launch_successful() { diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 0e188704..163abe72 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -49,8 +49,6 @@ pub struct ClientVpnConnectionHandlerResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "clientvpn")] fn example_clientvpn_connectionhandler_request() { diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs new file mode 100644 index 00000000..0f2eb03d --- /dev/null +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -0,0 +1,301 @@ +use std::collections::HashMap; + +use chrono::{DateTime, Utc}; +use serde::{ + de::{DeserializeOwned, Visitor}, + ser::Error as SerError, + Deserialize, Serialize, +}; +use serde_json::Value; + +/// `CloudWatchAlarm` is the generic outer structure of an event triggered by a CloudWatch Alarm. +/// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. +/// For examples of events that come via CloudWatch Alarms, +/// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#Lambda-action-payload +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarm +where + C: DeserializeOwned, + C: Serialize, + R: DeserializeOwned, + R: Serialize, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub alarm_arn: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + pub region: Option, + pub time: DateTime, + + #[serde(default, bound = "")] + pub alarm_data: CloudWatchAlarmData, +} + +/// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. +#[allow(unused)] +type CloudWatchMetricAlarm = CloudWatchAlarm; + +/// `CloudWatchCompositeAlarm` is the structure of an event triggered by CloudWatch composite alarms. +#[allow(unused)] +type CloudWatchCompositeAlarm = + CloudWatchAlarm; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmData +where + C: DeserializeOwned, + C: Serialize, + R: DeserializeOwned, + R: Serialize, +{ + pub alarm_name: String, + #[serde(default, bound = "")] + pub state: Option>, + #[serde(default, bound = "")] + pub previous_state: Option>, + #[serde(bound = "")] + pub configuration: C, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmState +where + R: DeserializeOwned, + R: Serialize, +{ + pub value: String, + pub reason: String, + #[serde(default, bound = "")] + pub reason_data: Option, + pub timestamp: DateTime, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricAlarmConfiguration { + #[serde(default)] + pub description: Option, + #[serde(default)] + pub metrics: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricDefinition { + pub id: String, + #[serde(default)] + pub return_data: bool, + pub metric_stat: CloudWatchMetricStatDefinition, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricStatDefinition { + #[serde(default)] + pub unit: Option, + #[serde(default)] + pub stat: Option, + pub period: u16, + pub metric: CloudWatchMetricStatMetricDefinition, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchMetricStatMetricDefinition { + #[serde(default)] + pub namespace: Option, + pub name: String, + pub dimensions: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchCompositeAlarmConfiguration { + pub alarm_rule: String, + pub actions_suppressor: String, + pub actions_suppressor_wait_period: u16, + pub actions_suppressor_extension_period: u16, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CloudWatchAlarmStateValue { + #[default] + Ok, + Alarm, + InsuficientData, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum CloudWatchAlarmStateReasonData { + Metric(CloudWatchAlarmStateReasonDataMetric), + Composite(ClodWatchAlarmStateReasonDataComposite), + Generic(Value), +} + +impl Default for CloudWatchAlarmStateReasonData { + fn default() -> Self { + Self::Generic(Value::String(String::new())) + } +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateReasonDataMetric { + pub version: String, + #[serde(default)] + pub query_date: Option>, + #[serde(default)] + pub start_date: Option>, + #[serde(default)] + pub unit: Option, + #[serde(default)] + pub statistic: Option, + pub period: u16, + #[serde(default)] + pub recent_datapoints: Vec, + #[serde(default)] + pub recent_lower_thresholds: Vec, + #[serde(default)] + pub recent_upper_thresholds: Vec, + pub threshold: f64, + #[serde(default)] + pub evaluated_datapoints: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateEvaluatedDatapoint { + pub timestamp: DateTime, + #[serde(default)] + pub sample_count: Option, + #[serde(default)] + pub value: Option, + #[serde(default)] + pub threshold: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClodWatchAlarmStateReasonDataComposite { + #[serde(default)] + pub triggering_alarms: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateTriggeringAlarm { + pub arn: String, + pub state: CloudWatchAlarmStateTriggeringAlarmState, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchAlarmStateTriggeringAlarmState { + pub timestamp: DateTime, + #[serde(default)] + pub value: CloudWatchAlarmStateValue, +} + +impl<'de> Deserialize<'de> for CloudWatchAlarmStateReasonData { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_any(ReasonDataVisitor) + } +} + +impl Serialize for CloudWatchAlarmStateReasonData { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let r = match self { + Self::Metric(m) => serde_json::to_string(m), + Self::Composite(m) => serde_json::to_string(m), + Self::Generic(m) => serde_json::to_string(m), + }; + let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {}", e)))?; + + serializer.serialize_str(&s) + } +} + +struct ReasonDataVisitor; + +impl<'de> Visitor<'de> for ReasonDataVisitor { + type Value = CloudWatchAlarmStateReasonData; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("a string with the alarm state reason data") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + if let Ok(metric) = serde_json::from_str::(v) { + return Ok(CloudWatchAlarmStateReasonData::Metric(metric)); + } + if let Ok(aggregate) = serde_json::from_str::(v) { + return Ok(CloudWatchAlarmStateReasonData::Composite(aggregate)); + } + Ok(CloudWatchAlarmStateReasonData::Generic(Value::String(v.to_owned()))) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_metric() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-metric.json"); + let parsed: CloudWatchMetricAlarm = serde_json::from_slice(data).unwrap(); + let state = parsed.alarm_data.previous_state.clone().unwrap(); + let data = state.reason_data.unwrap(); + match &data { + CloudWatchAlarmStateReasonData::Metric(d) => { + assert_eq!("1.0", d.version); + assert_eq!(5, d.evaluated_datapoints.len()); + } + _ => panic!("unexpected reason data {data:?}"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchMetricAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_composite() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-composite.json"); + let parsed: CloudWatchCompositeAlarm = serde_json::from_slice(data).unwrap(); + + let state = parsed.alarm_data.state.clone().unwrap(); + let data = state.reason_data.unwrap(); + match &data { + CloudWatchAlarmStateReasonData::Composite(d) => { + assert_eq!(1, d.triggering_alarms.len()); + assert_eq!( + CloudWatchAlarmStateValue::Alarm, + d.triggering_alarms.first().unwrap().state.value + ); + } + _ => panic!("unexpected reason data {data:?}"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 87687cfd..7d5edfaa 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -69,8 +69,6 @@ pub struct CodeCommitReference { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "code_commit")] fn example_code_commit_event() { diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index a3839f92..fdabcb6f 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -213,8 +213,6 @@ pub type CodeBuildTime = DateTime; mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codebuild")] fn example_codebuild_phase_change() { diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 2ab37a82..896f509f 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -69,8 +69,6 @@ pub struct CodeDeployEventDetail { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codedeploy")] fn example_codedeploy_deployment_event() { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index f26aa54f..22db26b1 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -81,8 +81,6 @@ pub struct CodePipelineEventDetailType { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codepipeline_cloudwatch")] fn example_codepipeline_action_execution_stage_change_event() { diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 6c5d75f6..888e77b7 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -117,8 +117,6 @@ pub struct CodePipelineArtifactCredentials { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "codepipeline_job")] fn example_codepipeline_job_event() { diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index c07c40a4..614f5278 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -464,8 +464,6 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "cognito")] fn example_cognito_event() { diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 0b03ecc5..7c06e13b 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -40,8 +40,6 @@ pub struct ConfigEvent { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "config")] fn example_config_event() { diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index bc640930..04f26eb5 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -94,8 +94,6 @@ pub type ConnectResponse = HashMap; mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "connect")] fn example_connect_event() { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 398f2dd5..33b977bd 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -252,8 +252,6 @@ mod test { use super::*; use chrono::TimeZone; - use serde_json; - #[test] #[cfg(feature = "dynamodb")] fn example_dynamodb_event() { diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 1ed91896..99c08f6d 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -61,8 +61,6 @@ pub struct EcrScanEventFindingSeverityCounts { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "ecr_scan")] fn example_ecr_image_scan_event() { diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 352342ec..6a0a13fd 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -76,8 +76,6 @@ pub struct KinesisFirehoseRecordMetadata { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "firehose")] fn example_firehose_event() { diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 31220b17..cf0ca246 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -76,8 +76,6 @@ pub struct IoTCoreCustomAuthorizerResponse { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "iot")] fn example_iot_custom_auth_request() { diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 4ec47d71..bf010b50 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -59,7 +59,6 @@ pub struct IoTOneClickPlacementInfo { #[cfg(test)] mod test { use super::*; - use serde_json; #[test] #[cfg(feature = "iot_1_click")] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index ac79e34b..2d2e4627 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -15,8 +15,6 @@ pub struct IoTButtonEvent { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "iot_button")] fn example_iot_button_event() { diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 8cd92bdf..27a1e921 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -35,8 +35,6 @@ pub struct KafkaRecord { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "kafka")] fn example_kafka_event() { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 0c43ae10..2b14cbed 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -75,8 +75,6 @@ pub struct KinesisRecord { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "kinesis")] fn example_kinesis_event() { diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index a058a249..d8f9403c 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -108,8 +108,6 @@ pub struct Attachment { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "lex")] fn example_lex_event() { diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index 28a0c82b..ea0ca7df 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -33,7 +33,11 @@ pub mod clientvpn; #[cfg(feature = "cloudformation")] pub mod cloudformation; -/// CloudWatch Events payload +/// AWS Lambda event definitions for CloudWatch alarms. +#[cfg(feature = "cloudwatch_alarms")] +pub mod cloudwatch_alarms; + +/// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] pub mod cloudwatch_events; diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 14a379a5..47f3c004 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -63,8 +63,6 @@ where mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "rabbitmq")] fn example_rabbitmq_event() { diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 128a5811..e062c7d2 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -92,8 +92,6 @@ pub struct S3Object { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "s3")] fn example_s3_event() { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 69e9907d..fe52b8a6 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -120,8 +120,6 @@ pub struct SessionIssuer { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "s3")] fn example_object_lambda_event_get_object_assumed_role() { diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index a4a97039..2a60957a 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -122,8 +122,6 @@ pub struct SimpleEmailDisposition { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "ses")] fn example_ses_lambda_event() { diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 3c859d3e..e9809630 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -179,8 +179,6 @@ pub struct MessageAttribute { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "sns")] fn my_example_sns_event() { diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 5c10a428..21359f3a 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -184,8 +184,6 @@ pub struct SqsApiMessage { mod test { use super::*; - use serde_json; - #[test] #[cfg(feature = "sqs")] fn example_sqs_event() { diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json new file mode 100644 index 00000000..f74b8f08 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite.json @@ -0,0 +1,30 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:SuppressionDemo.Main", + "accountId": "111122223333", + "time": "2023-08-04T12: 56: 46.138+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "CompositeDemo.Main", + "state": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August, 2023 12: 54: 46 UTC", + "reasonData": "{\"triggeringAlarms\": [{\"arn\": \"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\": {\"value\": \"ALARM\",\"timestamp\": \"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12: 56: 46.138+0000" + }, + "previousState": { + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1: 111122223333:alarm:CompositeDemo.FirstChild transitioned to ALARM at Friday 04 August,2023 12: 54: 46 UTC", + "reasonData": "{\"triggeringAlarms\": [{\"arn\": \"arn:aws:cloudwatch:us-east-1:111122223333:alarm:CompositeDemo.FirstChild\",\"state\": {\"value\": \"ALARM\",\"timestamp\": \"2023-08-04T12:54:46.138+0000\"}}]}", + "timestamp": "2023-08-04T12: 54: 46.138+0000", + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod" + }, + "configuration": { + "alarmRule": "ALARM(CompositeDemo.FirstChild) OR ALARM(CompositeDemo.SecondChild)", + "actionsSuppressor": "CompositeDemo.ActionsSuppressor", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json new file mode 100644 index 00000000..04a17649 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-metric.json @@ -0,0 +1,42 @@ +{ + "source": "aws.cloudwatch", + "alarmArn": "arn:aws:cloudwatch:us-east-1: 444455556666:alarm:lambda-demo-metric-alarm", + "accountId": "444455556666", + "time": "2023-08-04T12: 36: 15.490+0000", + "region": "us-east-1", + "alarmData": { + "alarmName": "lambda-demo-metric-alarm", + "state": { + "value": "ALARM", + "reason": "test", + "timestamp": "2023-08-04T12: 36: 15.490+0000" + }, + "previousState": { + "value": "INSUFFICIENT_DATA", + "reason": "Insufficient Data: 5 datapoints were unknown.", + "reasonData": "{\"version\": \"1.0\", \"queryDate\": \"2023-08-04T12: 31: 29.591+0000\", \"statistic\": \"Average\", \"period\": 60, \"recentDatapoints\": [], \"threshold\": 5.0, \"evaluatedDatapoints\": [{\"timestamp\": \"2023-08-04T12: 30: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 29: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 28: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 27: 00.000+0000\"},{\"timestamp\": \"2023-08-04T12: 26: 00.000+0000\"}]}", + "timestamp": "2023-08-04T12: 31: 29.595+0000" + }, + "configuration": { + "description": "Metric Alarm to test Lambda actions", + "metrics": [ + { + "id": "1234e046-06f0-a3da-9534-EXAMPLEe4c", + "metricStat": { + "metric": { + "namespace": "AWS/Logs", + "name": "CallCount", + "dimensions": { + "InstanceId": "i-12345678" + } + }, + "period": 60, + "stat": "Average", + "unit": "Percent" + }, + "returnData": true + } + ] + } + } +} \ No newline at end of file diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index aa0d5495..4fd294e6 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -45,7 +45,11 @@ pub use event::clientvpn; #[cfg(feature = "cloudformation")] pub use event::cloudformation; -/// CloudWatch Events payload +/// AWS Lambda event definitions for CloudWatch alarms. +#[cfg(feature = "cloudwatch_alarms")] +pub use event::cloudwatch_alarms; + +/// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] pub use event::cloudwatch_events; diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 9f035995..edc9beb5 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -66,8 +66,6 @@ pub struct TimeWindowEventResponseProperties { mod test { use super::*; - use serde_json; - #[test] fn test_window_deserializer() { let v = serde_json::json!({ From f00aa0416d83828db5c509aec883e8ed5356aeb8 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Fri, 29 Dec 2023 02:41:21 +0900 Subject: [PATCH 058/211] Fix parsing failure in CloudWatchAlarmStateValue. (#761) --- lambda-events/src/event/cloudwatch_alarms/mod.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 0f2eb03d..aa8f6dd1 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -69,7 +69,8 @@ where R: DeserializeOwned, R: Serialize, { - pub value: String, + #[serde(default)] + pub value: CloudWatchAlarmStateValue, pub reason: String, #[serde(default, bound = "")] pub reason_data: Option, @@ -129,7 +130,7 @@ pub enum CloudWatchAlarmStateValue { #[default] Ok, Alarm, - InsuficientData, + InsufficientData, } #[derive(Clone, Debug, PartialEq)] From ee7dcd7c0340f097e573e4941ebd687fad551d39 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 28 Dec 2023 10:48:06 -0800 Subject: [PATCH 059/211] Remove unused imports. (#762) Signed-off-by: David Calavera --- lambda-http/src/streaming.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 601e699b..217c4564 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -2,14 +2,10 @@ use crate::http::header::SET_COOKIE; use crate::tower::ServiceBuilder; use crate::Request; use crate::{request::LambdaRequest, RequestExt}; -pub use aws_lambda_events::encodings::Body as LambdaEventBody; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; -pub use lambda_runtime::{ - self, service_fn, tower, tower::ServiceExt, Error, FunctionResponse, LambdaEvent, MetadataPrelude, Service, - StreamResponse, -}; +pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; use std::fmt::{Debug, Display}; use std::pin::Pin; use std::task::{Context, Poll}; From 3b32d090d12b8c790d499224a2eee10f0d460b22 Mon Sep 17 00:00:00 2001 From: Benjamen Pyle Date: Fri, 29 Dec 2023 10:42:52 -0600 Subject: [PATCH 060/211] Add Event Definition for CognitoEventUserPoolsPreTokenGenV2 (#764) The added structs allow for the processing of Version 2 Cognito PreToken generation in a Lambda The V2 payloads allow for customization of the Access Token in addition to the ID Token which was already supported --- lambda-events/src/event/cognito/mod.rs | 79 +++++++++++++++++++ ...ent-userpools-pretokengen-v2-incoming.json | 33 ++++++++ ...ognito-event-userpools-pretokengen-v2.json | 58 ++++++++++++++ 3 files changed, 170 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 614f5278..decc31a5 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -213,6 +213,65 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { pub claims_override_details: Option, } +/// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve +/// credentials, allowing a Lambda to perform insert, suppress or override claims. This is the Version 2 Payload +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenV2 { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreTokenGenRequestV2, + pub response: CognitoEventUserPoolsPreTokenGenResponseV2, +} + +/// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + pub group_configuration: GroupConfiguration, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, + pub scopes: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { + pub claims_and_scope_override_details: Option, +} + +/// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClaimsAndScopeOverrideDetailsV2 { + pub group_override_details: GroupConfiguration, + pub id_token_generation: Option, + pub access_token_generation: Option, +} + +/// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoIdTokenGenerationV2 { + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, +} + +/// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoAccessTokenGenerationV2 { + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, + pub scopes_to_add: Vec, + pub scopes_to_suppress: Vec, +} + /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -608,6 +667,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen_v2_incoming() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json"); + let parsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_pretokengen() { @@ -618,6 +687,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_v2_pretokengen() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-v2.json"); + let parsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGenV2 = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_verify_auth_challenge() { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json new file mode 100644 index 00000000..3376d6e0 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json @@ -0,0 +1,33 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsOverrideDetails": null + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json new file mode 100644 index 00000000..f7ccfe2f --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json @@ -0,0 +1,58 @@ +{ + "version": "1", + "triggerSource": "PreTokenGen", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" + }, + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsAndScopeOverrideDetails": { + "idTokenGeneration": { + "claimsToAddOrOverride": { + "string": "string" + }, + "claimsToSuppress": ["string", "string"] + }, + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" + }, + "claimsToSuppress": ["email", "phone"], + "scopesToAdd": ["scope-B", "scope-B"], + "scopesToSuppress": ["scope-C", "scope-D"] + }, + "groupOverrideDetails": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } + } + } +} From 3566f424552eb01af6fef3674c9cc5558b3518df Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 16 Jan 2024 02:39:49 +0800 Subject: [PATCH 061/211] Add events pass-through feature in lambda-http crate (#775) * Add pass-through support for non-http triggers * Fix linting warming * Remove deserialize_error test This test won't fail when `pass-through` feature is enabled. --- lambda-http/Cargo.toml | 3 ++- lambda-http/src/deserializer.rs | 48 ++++++++++++++++----------------- lambda-http/src/request.rs | 32 ++++++++++++++++++++++ lambda-http/src/response.rs | 11 ++++++++ 4 files changed, 68 insertions(+), 26 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index c3ec425e..2e1bcdfa 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -21,6 +21,7 @@ apigw_rest = [] apigw_http = [] apigw_websockets = [] alb = [] +pass_through = [] [dependencies] base64 = { workspace = true } @@ -37,7 +38,7 @@ mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" +serde_json = { version = "1.0", features = ["raw_value"] } serde_urlencoded = "0.7" tokio-stream = "0.1.2" url = "2.2" diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 7756629c..2584c0ad 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -1,43 +1,48 @@ use crate::request::LambdaRequest; +#[cfg(feature = "alb")] +use aws_lambda_events::alb::AlbTargetGroupRequest; +#[cfg(feature = "apigw_rest")] +use aws_lambda_events::apigw::ApiGatewayProxyRequest; +#[cfg(feature = "apigw_http")] +use aws_lambda_events::apigw::ApiGatewayV2httpRequest; +#[cfg(feature = "apigw_websockets")] +use aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest; use serde::{de::Error, Deserialize}; +use serde_json::value::RawValue; const ERROR_CONTEXT: &str = "this function expects a JSON payload from Amazon API Gateway, Amazon Elastic Load Balancer, or AWS Lambda Function URLs, but the data doesn't match any of those services' events"; +#[cfg(feature = "pass_through")] +const PASS_THROUGH_ENABLED: bool = true; + impl<'de> Deserialize<'de> for LambdaRequest { fn deserialize(deserializer: D) -> Result where D: serde::Deserializer<'de>, { - let content = match serde::__private::de::Content::deserialize(deserializer) { - Ok(content) => content, - Err(err) => return Err(err), - }; + let raw_value: Box = Box::deserialize(deserializer)?; + let data = raw_value.get(); + #[cfg(feature = "apigw_rest")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayProxyRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::ApiGatewayV1(res)); } #[cfg(feature = "apigw_http")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayV2httpRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::ApiGatewayV2(res)); } #[cfg(feature = "alb")] - if let Ok(res) = - aws_lambda_events::alb::AlbTargetGroupRequest::deserialize(serde::__private::de::ContentRefDeserializer::< - D::Error, - >::new(&content)) - { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::Alb(res)); } #[cfg(feature = "apigw_websockets")] - if let Ok(res) = aws_lambda_events::apigw::ApiGatewayWebsocketProxyRequest::deserialize( - serde::__private::de::ContentRefDeserializer::::new(&content), - ) { + if let Ok(res) = serde_json::from_str::(data) { return Ok(LambdaRequest::WebSocket(res)); } + #[cfg(feature = "pass_through")] + if PASS_THROUGH_ENABLED { + return Ok(LambdaRequest::PassThrough(data.to_string())); + } Err(Error::custom(ERROR_CONTEXT)) } @@ -104,11 +109,4 @@ mod tests { other => panic!("unexpected request variant: {:?}", other), } } - - #[test] - fn test_deserialize_error() { - let err = serde_json::from_str::("{\"body\": {}}").unwrap_err(); - - assert_eq!(ERROR_CONTEXT, err.to_string()); - } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index ad86e5a5..c98f5c17 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -51,6 +51,8 @@ pub enum LambdaRequest { Alb(AlbTargetGroupRequest), #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequest), + #[cfg(feature = "pass_through")] + PassThrough(String), } impl LambdaRequest { @@ -67,6 +69,8 @@ impl LambdaRequest { LambdaRequest::Alb { .. } => RequestOrigin::Alb, #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket { .. } => RequestOrigin::WebSocket, + #[cfg(feature = "pass_through")] + LambdaRequest::PassThrough { .. } => RequestOrigin::PassThrough, #[cfg(not(any( feature = "apigw_rest", feature = "apigw_http", @@ -97,6 +101,9 @@ pub enum RequestOrigin { /// API Gateway WebSocket #[cfg(feature = "apigw_websockets")] WebSocket, + /// PassThrough request origin + #[cfg(feature = "pass_through")] + PassThrough, } #[cfg(feature = "apigw_http")] @@ -338,6 +345,26 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< req } +#[cfg(feature = "pass_through")] +fn into_pass_through_request(data: String) -> http::Request { + let mut builder = http::Request::builder(); + + let headers = builder.headers_mut().unwrap(); + headers.insert("Content-Type", "application/json".parse().unwrap()); + + update_xray_trace_id_header(headers); + + let raw_path = "/events"; + + builder + .method(http::Method::POST) + .uri(raw_path) + .extension(RawHttpPath(raw_path.to_string())) + .extension(RequestContext::PassThrough) + .body(Body::from(data)) + .expect("failed to build request") +} + #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] fn apigw_path_with_stage(stage: &Option, path: &str) -> String { if env::var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH").is_ok() { @@ -375,6 +402,9 @@ pub enum RequestContext { /// WebSocket request context #[cfg(feature = "apigw_websockets")] WebSocket(ApiGatewayWebsocketProxyRequestContext), + /// Custom request context + #[cfg(feature = "pass_through")] + PassThrough, } /// Converts LambdaRequest types into `http::Request` types @@ -389,6 +419,8 @@ impl From for http::Request { LambdaRequest::Alb(alb) => into_alb_request(alb), #[cfg(feature = "apigw_websockets")] LambdaRequest::WebSocket(ag) => into_websocket_request(ag), + #[cfg(feature = "pass_through")] + LambdaRequest::PassThrough(data) => into_pass_through_request(data), } } } diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index d26ef838..cc721d46 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -46,6 +46,8 @@ pub enum LambdaResponse { ApiGatewayV2(ApiGatewayV2httpResponse), #[cfg(feature = "alb")] Alb(AlbTargetGroupResponse), + #[cfg(feature = "pass_through")] + PassThrough(serde_json::Value), } /// Transformation from http type to internal type @@ -114,6 +116,15 @@ impl LambdaResponse { headers: headers.clone(), multi_value_headers: headers, }), + #[cfg(feature = "pass_through")] + RequestOrigin::PassThrough => { + match body { + // text body must be a valid json string + Some(Body::Text(body)) => {LambdaResponse::PassThrough(serde_json::from_str(&body).unwrap_or_default())}, + // binary body and other cases return Value::Null + _ => LambdaResponse::PassThrough(serde_json::Value::Null), + } + } #[cfg(not(any( feature = "apigw_rest", feature = "apigw_http", From 4cf514a88e331f4ab92146efe0ed7ed5fd236265 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Tue, 16 Jan 2024 03:40:00 +0900 Subject: [PATCH 062/211] Add missing fields for suppressor alarm (#774) * Add missing fields for suppressor alarm * Add test for suppresor alarm * Fix format error --- .../src/event/cloudwatch_alarms/mod.rs | 19 ++++++++++ ...alarm-composite-with-suppressor-alarm.json | 35 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index aa8f6dd1..5c4b704d 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -75,6 +75,8 @@ where #[serde(default, bound = "")] pub reason_data: Option, pub timestamp: DateTime, + pub actions_suppressed_by: Option, + pub actions_suppressed_reason: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -299,4 +301,21 @@ mod test { let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "cloudwatch_alarms")] + fn example_cloudwatch_alarm_composite_with_suppressor_alarm() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json"); + let parsed: CloudWatchCompositeAlarm = serde_json::from_slice(data).unwrap(); + let state = parsed.alarm_data.state.clone().unwrap(); + assert_eq!("WaitPeriod", state.actions_suppressed_by.unwrap()); + assert_eq!( + "Actions suppressed by WaitPeriod", + state.actions_suppressed_reason.unwrap() + ); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudWatchCompositeAlarm = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json new file mode 100644 index 00000000..26ce1c9b --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-composite-with-suppressor-alarm.json @@ -0,0 +1,35 @@ +{ + "version": "0", + "id": "d3dfc86d-384d-24c8-0345-9f7986db0b80", + "detail-type": "CloudWatch Alarm State Change", + "source": "aws.cloudwatch", + "account": "123456789012", + "time": "2022-07-22T15:57:45Z", + "region": "us-east-1", + "resources": [ + "arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServiceAggregatedAlarm" + ], + "alarmData": { + "alarmName": "ServiceAggregatedAlarm", + "state": { + "actionsSuppressedBy": "WaitPeriod", + "actionsSuppressedReason": "Actions suppressed by WaitPeriod", + "value": "ALARM", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.FirstChild transitioned to ALARM at Friday 22 July, 2022 15:57:45 UTC", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"ALARM\",\"timestamp\":\"2022-07-22T15:57:45.394+0000\"}}]}", + "timestamp": "2022-07-22T15:57:45.394+0000" + }, + "previousState": { + "value": "OK", + "reason": "arn:aws:cloudwatch:us-east-1:123456789012:alarm:SuppressionDemo.EventBridge.Main was created and its alarm rule evaluates to OK", + "reasonData": "{\"triggeringAlarms\":[{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:TotalNetworkTrafficTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:57.770+0000\"}},{\"arn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:ServerCpuTooHigh\",\"state\":{\"value\":\"OK\",\"timestamp\":\"2022-07-14T16:28:54.191+0000\"}}]}", + "timestamp": "2022-07-22T15:56:14.552+0000" + }, + "configuration": { + "alarmRule": "ALARM(ServerCpuTooHigh) OR ALARM(TotalNetworkTrafficTooHigh)", + "actionsSuppressor": "ServiceMaintenanceAlarm", + "actionsSuppressorWaitPeriod": 120, + "actionsSuppressorExtensionPeriod": 180 + } + } +} \ No newline at end of file From b4914f884c3d40290022a3558a3195bd04bd9897 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 16 Jan 2024 08:54:55 +0800 Subject: [PATCH 063/211] Release lambda-http 0.9.1 (#777) --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 2e1bcdfa..4c84de21 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.0" +version = "0.9.1" authors = [ "David Calavera ", "Harold Sun ", From 6e03ba7b5d7d91611e6d57ddf7499a6f1d54e260 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 15 Jan 2024 17:00:15 -0800 Subject: [PATCH 064/211] Add a default implementation for Context. (#778) --- lambda-runtime/src/types.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index f2a36073..85f6af78 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -94,6 +94,20 @@ pub struct Context { pub env_config: RefConfig, } +impl Default for Context { + fn default() -> Context { + Context { + request_id: "".to_owned(), + deadline: 0, + invoked_function_arn: "".to_owned(), + xray_trace_id: None, + client_context: None, + identity: None, + env_config: std::sync::Arc::new(crate::Config::default()), + } + } +} + impl Context { /// Create a new [Context] struct based on the fuction configuration /// and the incoming request data. From 6e1c1540daefb37177d2004c837af7e52c74b707 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 15 Jan 2024 18:56:38 -0800 Subject: [PATCH 065/211] Release lambda_runtime 0.9.1 (#779) --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index f3c8e053..ccbb3b2e 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.0" +version = "0.9.1" authors = [ "David Calavera ", "Harold Sun ", From 99eb0319a2c7ee229f7c88d67de02ae696729eeb Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 16 Jan 2024 01:16:09 -0800 Subject: [PATCH 066/211] Test runtime with httpmock. (#780) * Test runtime with httpmock. - Remove generic runtime over the client. - Make tests use httpmock for more concise assertions. Signed-off-by: David Calavera * Bump MSRV to 1.65. Fixes compilation issues with the regex crate. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime-api-client/src/lib.rs | 41 +-- lambda-runtime/Cargo.toml | 3 +- lambda-runtime/src/lib.rs | 345 +++++++++----------------- lambda-runtime/src/simulated.rs | 133 ---------- 8 files changed, 134 insertions(+), 396 deletions(-) delete mode 100644 lambda-runtime/src/simulated.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 4e5fb34d..caee1131 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 7365bc64..7165a281 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 9657a840..026c20e0 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.64.0" # Current MSRV + - "1.65.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index ef43ba74..eeeaf2fa 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.64, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.65, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 15185f81..ec7418ba 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -6,9 +6,8 @@ //! the AWS Lambda Runtime API. use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; use hyper::body::Incoming; -use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; +use hyper_util::client::legacy::connect::HttpConnector; use std::{convert::TryInto, fmt::Debug}; -use tower_service::Service; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); @@ -20,16 +19,16 @@ pub mod body; /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] -pub struct Client { +pub struct Client { /// The runtime API URI pub base: Uri, /// The client that manages the API connections - pub client: hyper_util::client::legacy::Client, + pub client: hyper_util::client::legacy::Client, } impl Client { /// Create a builder struct to configure the client. - pub fn builder() -> ClientBuilder { + pub fn builder() -> ClientBuilder { ClientBuilder { connector: HttpConnector::new(), uri: None, @@ -37,10 +36,7 @@ impl Client { } } -impl Client -where - C: Connect + Sync + Send + Clone + 'static, -{ +impl Client { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. pub async fn call(&self, req: Request) -> Result, BoxError> { @@ -49,7 +45,7 @@ where } /// Create a new client with a given base URI and HTTP connector. - pub fn with(base: Uri, connector: C) -> Self { + fn with(base: Uri, connector: HttpConnector) -> Self { let client = hyper_util::client::legacy::Client::builder(hyper_util::rt::TokioExecutor::new()) .http1_max_buf_size(1024 * 1024) .build(connector); @@ -80,26 +76,14 @@ where } /// Builder implementation to construct any Runtime API clients. -pub struct ClientBuilder = HttpConnector> { - connector: C, +pub struct ClientBuilder { + connector: HttpConnector, uri: Option, } -impl ClientBuilder -where - C: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, -{ +impl ClientBuilder { /// Create a new builder with a given HTTP connector. - pub fn with_connector(self, connector: C2) -> ClientBuilder - where - C2: Service + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, - { + pub fn with_connector(self, connector: HttpConnector) -> ClientBuilder { ClientBuilder { connector, uri: self.uri, @@ -113,10 +97,7 @@ where } /// Create the new client to interact with the Runtime API. - pub fn build(self) -> Result, Error> - where - C: Connect + Sync + Send + Clone + 'static, - { + pub fn build(self) -> Result { let uri = match self.uri { Some(uri) => uri, None => { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index ccbb3b2e..221aa6f0 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -51,6 +51,7 @@ tower = { workspace = true, features = ["util"] } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] +httpmock = "0.7.0" hyper-util = { workspace = true, features = [ "client", "client-legacy", @@ -59,4 +60,4 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } -pin-project-lite = { workspace = true } \ No newline at end of file +pin-project-lite = { workspace = true } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 10dfce92..3a91d943 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -11,7 +11,6 @@ use bytes::Bytes; use futures::FutureExt; use http_body_util::BodyExt; use hyper::{body::Incoming, http::Request}; -use hyper_util::client::legacy::connect::{Connect, Connection, HttpConnector}; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ @@ -28,8 +27,6 @@ use tracing::{error, trace, Instrument}; mod deserializer; mod requests; -#[cfg(test)] -mod simulated; /// Utilities for Lambda Streaming functions. pub mod streaming; /// Types available to a Lambda function. @@ -85,18 +82,12 @@ where service_fn(move |req: LambdaEvent| f(req.payload, req.context)) } -struct Runtime = HttpConnector> { - client: Client, +struct Runtime { + client: Client, config: RefConfig, } -impl Runtime -where - C: Service + Connect + Clone + Send + Sync + Unpin + 'static, - C::Future: Unpin + Send, - C::Error: Into>, - C::Response: Connection + Unpin + Send + 'static, -{ +impl Runtime { async fn run( &self, incoming: impl Stream, Error>> + Send, @@ -196,13 +187,7 @@ where } } -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ -where - C: Service + Connect + Clone + Send + Sync + Unpin + 'static, - >::Future: Unpin + Send, - >::Error: Into>, - >::Response: Connection + Unpin + Send + 'static, -{ +fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ { async_stream::stream! { loop { trace!("Waiting for next event (incoming loop)"); @@ -276,231 +261,135 @@ where mod endpoint_tests { use crate::{ incoming, - requests::{ - EventCompletionRequest, EventErrorRequest, IntoRequest, IntoResponse, NextEventRequest, NextEventResponse, - }, - simulated, + requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, types::Diagnostic, Config, Error, Runtime, }; use futures::future::BoxFuture; - use http::{uri::PathAndQuery, HeaderValue, Method, Request, Response, StatusCode, Uri}; - use hyper::body::Incoming; - use hyper::rt::{Read, Write}; - use hyper::service::service_fn; - - use hyper_util::server::conn::auto::Builder; - use lambda_runtime_api_client::{body::Body, Client}; - use serde_json::json; - use simulated::DuplexStreamWrapper; - use std::{convert::TryFrom, env, sync::Arc}; - use tokio::{ - io, select, - sync::{self, oneshot}, - }; - use tokio_stream::StreamExt; + use http::{HeaderValue, StatusCode}; + use http_body_util::BodyExt; + use httpmock::prelude::*; - #[cfg(test)] - async fn next_event(req: &Request) -> Result, Error> { - let path = "/2018-06-01/runtime/invocation/next"; - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri().path_and_query().unwrap(), &PathAndQuery::from_static(path)); - let body = json!({"message": "hello"}); - - let rsp = NextEventResponse { - request_id: "8476a536-e9f4-11e8-9739-2dfe598c3fcd", - deadline: 1_542_409_706_888, - arn: "arn:aws:lambda:us-east-2:123456789012:function:custom-runtime", - trace_id: "Root=1-5bef4de7-ad49b0e87f6ef6c87fc2e700;Parent=9a9197af755a6419", - body: serde_json::to_vec(&body)?, - }; - rsp.into_rsp() - } - - #[cfg(test)] - async fn complete_event(req: &Request, id: &str) -> Result, Error> { - assert_eq!(Method::POST, req.method()); - let rsp = Response::builder() - .status(StatusCode::ACCEPTED) - .body(Body::empty()) - .expect("Unable to construct response"); - - let expected = format!("/2018-06-01/runtime/invocation/{id}/response"); - assert_eq!(expected, req.uri().path()); - - Ok(rsp) - } - - #[cfg(test)] - async fn event_err(req: &Request, id: &str) -> Result, Error> { - let expected = format!("/2018-06-01/runtime/invocation/{id}/error"); - assert_eq!(expected, req.uri().path()); - - assert_eq!(req.method(), Method::POST); - let header = "lambda-runtime-function-error-type"; - let expected = "unhandled"; - assert_eq!(req.headers()[header], HeaderValue::try_from(expected)?); - - let rsp = Response::builder().status(StatusCode::ACCEPTED).body(Body::empty())?; - Ok(rsp) - } - - #[cfg(test)] - async fn handle_incoming(req: Request) -> Result, Error> { - let path: Vec<&str> = req - .uri() - .path_and_query() - .expect("PathAndQuery not found") - .as_str() - .split('/') - .collect::>(); - match path[1..] { - ["2018-06-01", "runtime", "invocation", "next"] => next_event(&req).await, - ["2018-06-01", "runtime", "invocation", id, "response"] => complete_event(&req, id).await, - ["2018-06-01", "runtime", "invocation", id, "error"] => event_err(&req, id).await, - ["2018-06-01", "runtime", "init", "error"] => unimplemented!(), - _ => unimplemented!(), - } - } - - #[cfg(test)] - async fn handle(io: I, rx: oneshot::Receiver<()>) -> Result<(), Error> - where - I: Read + Write + Unpin + 'static, - { - use hyper_util::rt::TokioExecutor; - - let builder = Builder::new(TokioExecutor::new()); - let conn = builder.serve_connection(io, service_fn(handle_incoming)); - select! { - _ = rx => { - Ok(()) - } - res = conn => { - match res { - Ok(()) => Ok(()), - Err(e) => { - Err(e) - } - } - } - } - } + use lambda_runtime_api_client::Client; + use std::{env, sync::Arc}; + use tokio_stream::StreamExt; #[tokio::test] async fn test_next_event() -> Result<(), Error> { - let base = Uri::from_static("http://localhost:9001"); - let (client, server) = io::duplex(64); - - let (tx, rx) = sync::oneshot::channel(); - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let mock = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let req = NextEventRequest.into_req()?; let rsp = client.call(req).await.expect("Unable to send request"); + mock.assert_async().await; assert_eq!(rsp.status(), StatusCode::OK); - let header = "lambda-runtime-deadline-ms"; - assert_eq!(rsp.headers()[header], &HeaderValue::try_from("1542409706888")?); - - // shutdown server... - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + assert_eq!( + rsp.headers()["lambda-runtime-aws-request-id"], + &HeaderValue::from_static(request_id) + ); + assert_eq!( + rsp.headers()["lambda-runtime-deadline-ms"], + &HeaderValue::from_static(deadline) + ); + + let body = rsp.into_body().collect().await?.to_bytes(); + assert_eq!("{}", std::str::from_utf8(&body)?); + Ok(()) } #[tokio::test] async fn test_ok_response() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") + .body("\"{}\""); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; - let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "done"); + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); let req = req.into_req()?; let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) } #[tokio::test] async fn test_error_response() -> Result<(), Error> { - let (client, server) = io::duplex(200); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let diagnostic = Diagnostic { + error_type: "InvalidEventDataError", + error_message: "Error parsing event data", + }; + let body = serde_json::to_string(&diagnostic)?; + + let server = MockServer::start(); + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") + .header("lambda-runtime-function-error-type", "unhandled") + .body(body); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::with(base, conn); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let req = EventErrorRequest { request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - diagnostic: Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", - }, + diagnostic, }; let req = req.into_req()?; let rsp = client.call(req).await?; - assert_eq!(rsp.status(), StatusCode::ACCEPTED); - - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) } #[tokio::test] async fn successful_end_to_end_run() -> Result<(), Error> { - let (client, server) = io::duplex(64); - let (tx, rx) = sync::oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .body("{}"); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::builder() - .with_endpoint(base) - .with_connector(conn) - .build() - .expect("Unable to build client"); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; async fn func(event: crate::LambdaEvent) -> Result { let (event, _) = event.into_parts(); @@ -510,7 +399,7 @@ mod endpoint_tests { // set env vars needed to init Config if they are not already set in the environment if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { - env::set_var("AWS_LAMBDA_RUNTIME_API", "http://localhost:9001"); + env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); } if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); @@ -537,35 +426,37 @@ mod endpoint_tests { let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; - // shutdown server - tx.send(()).expect("Receiver has been dropped"); - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) } async fn run_panicking_handler(func: F) -> Result<(), Error> where F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result>, { - let (client, server) = io::duplex(64); - let (_tx, rx) = oneshot::channel(); - let base = Uri::from_static("http://localhost:9001"); - - let server = tokio::spawn(async { - handle(DuplexStreamWrapper::new(server), rx) - .await - .expect("Unable to handle request"); + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .header("lambda-runtime-function-error-type", "unhandled"); + then.status(200).body(""); }); - let conn = simulated::Connector::with(base.clone(), DuplexStreamWrapper::new(client))?; - let client = Client::builder() - .with_endpoint(base) - .with_connector(conn) - .build() - .expect("Unable to build client"); + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; let f = crate::service_fn(func); @@ -582,11 +473,9 @@ mod endpoint_tests { let incoming = incoming(client).take(1); runtime.run(incoming, f).await?; - match server.await { - Ok(_) => Ok(()), - Err(e) if e.is_panic() => Err::<(), Error>(e.into()), - Err(_) => unreachable!("This branch shouldn't be reachable"), - } + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) } #[tokio::test] diff --git a/lambda-runtime/src/simulated.rs b/lambda-runtime/src/simulated.rs deleted file mode 100644 index 018664fe..00000000 --- a/lambda-runtime/src/simulated.rs +++ /dev/null @@ -1,133 +0,0 @@ -use http::Uri; -use hyper::rt::{Read, Write}; -use hyper_util::client::legacy::connect::{Connected, Connection}; -use pin_project_lite::pin_project; -use std::{ - collections::HashMap, - future::Future, - pin::Pin, - sync::{Arc, Mutex}, - task::{Context, Poll}, -}; -use tokio::io::DuplexStream; - -use crate::Error; - -#[derive(Clone)] -pub struct Connector { - inner: Arc>>, -} - -pin_project! { -pub struct DuplexStreamWrapper { - #[pin] - inner: DuplexStream, -} -} - -impl DuplexStreamWrapper { - pub(crate) fn new(inner: DuplexStream) -> DuplexStreamWrapper { - DuplexStreamWrapper { inner } - } -} - -impl Connector { - pub fn new() -> Self { - #[allow(clippy::mutable_key_type)] - let map = HashMap::new(); - Connector { - inner: Arc::new(Mutex::new(map)), - } - } - - pub fn insert(&self, uri: Uri, stream: DuplexStreamWrapper) -> Result<(), Error> { - match self.inner.lock() { - Ok(mut map) => { - map.insert(uri, stream); - Ok(()) - } - Err(_) => Err("mutex was poisoned".into()), - } - } - - pub fn with(uri: Uri, stream: DuplexStreamWrapper) -> Result { - let connector = Connector::new(); - match connector.insert(uri, stream) { - Ok(_) => Ok(connector), - Err(e) => Err(e), - } - } -} - -impl tower::Service for Connector { - type Response = DuplexStreamWrapper; - type Error = crate::Error; - #[allow(clippy::type_complexity)] - type Future = Pin> + Send>>; - - fn call(&mut self, uri: Uri) -> Self::Future { - let res = match self.inner.lock() { - Ok(mut map) if map.contains_key(&uri) => Ok(map.remove(&uri).unwrap()), - Ok(_) => Err(format!("Uri {uri} is not in map").into()), - Err(_) => Err("mutex was poisoned".into()), - }; - Box::pin(async move { res }) - } - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } -} - -impl Connection for DuplexStreamWrapper { - fn connected(&self) -> Connected { - Connected::new() - } -} - -impl Read for DuplexStreamWrapper { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - mut buf: hyper::rt::ReadBufCursor<'_>, - ) -> Poll> { - let n = unsafe { - let mut tbuf = tokio::io::ReadBuf::uninit(buf.as_mut()); - match tokio::io::AsyncRead::poll_read(self.project().inner, cx, &mut tbuf) { - Poll::Ready(Ok(())) => tbuf.filled().len(), - other => return other, - } - }; - - unsafe { - buf.advance(n); - } - Poll::Ready(Ok(())) - } -} - -impl Write for DuplexStreamWrapper { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { - tokio::io::AsyncWrite::poll_write(self.project().inner, cx, buf) - } - - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_flush(self.project().inner, cx) - } - - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - tokio::io::AsyncWrite::poll_shutdown(self.project().inner, cx) - } - - fn is_write_vectored(&self) -> bool { - tokio::io::AsyncWrite::is_write_vectored(&self.inner) - } - - fn poll_write_vectored( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - bufs: &[std::io::IoSlice<'_>], - ) -> Poll> { - tokio::io::AsyncWrite::poll_write_vectored(self.project().inner, cx, bufs) - } -} From ea4a8f607fbde03ff9d3068ebedd94f2ddccf88b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Sumis=C5=82awski?= Date: Thu, 18 Jan 2024 22:55:27 +0100 Subject: [PATCH 067/211] Make EcrScanEvent severities optional (#783) * Make EcrScanEvent severities optional * Add the ecr example json file needed by test --- lambda-events/src/event/ecr_scan/mod.rs | 28 +++++++++++++++---- ...ge-scan-event-with-missing-severities.json | 22 +++++++++++++++ 2 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 99c08f6d..5502e81a 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -43,18 +43,24 @@ pub struct EcrScanEventDetailType { #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { + #[serde(default)] #[serde(rename = "CRITICAL")] - pub critical: i64, + pub critical: Option, + #[serde(default)] #[serde(rename = "HIGH")] - pub high: i64, + pub high: Option, + #[serde(default)] #[serde(rename = "MEDIUM")] - pub medium: i64, + pub medium: Option, + #[serde(default)] #[serde(rename = "LOW")] - pub low: i64, + pub low: Option, + #[serde(default)] #[serde(rename = "INFORMATIONAL")] - pub informational: i64, + pub informational: Option, + #[serde(default)] #[serde(rename = "UNDEFINED")] - pub undefined: i64, + pub undefined: Option, } #[cfg(test)] @@ -70,4 +76,14 @@ mod test { let reparsed: EcrScanEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "ecr_scan")] + fn example_ecr_image_scan_event_with_missing_severities() { + let data = include_bytes!("../../fixtures/example-ecr-image-scan-event-with-missing-severities.json"); + let parsed: EcrScanEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EcrScanEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json b/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json new file mode 100644 index 00000000..883eb2d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-ecr-image-scan-event-with-missing-severities.json @@ -0,0 +1,22 @@ +{ + "version": "0", + "id": "85fc3613-e913-7fc4-a80c-a3753e4aa9ae", + "detail-type": "ECR Image Scan", + "source": "aws.ecr", + "account": "123456789012", + "time": "2019-10-29T02:36:48Z", + "region": "us-east-1", + "resources": [ + "arn:aws:ecr:us-east-1:123456789012:repository/my-repository-name" + ], + "detail": { + "scan-status": "COMPLETE", + "repository-name": "my-repository-name", + "finding-severity-counts": { + "CRITICAL": 10, + "MEDIUM": 9 + }, + "image-digest": "sha256:7f5b2640fe6fb4f46592dfd3410c4a79dac4f89e4782432e0378abcd1234", + "image-tags": [] + } +} \ No newline at end of file From 355627ea09b295146e64f488a9d1f517293996a2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 18 Jan 2024 20:03:12 -0800 Subject: [PATCH 068/211] Document APIGW stage behavior. (#784) Indicate how to disable the stage in the path. Signed-off-by: David Calavera --- lambda-http/README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lambda-http/README.md b/lambda-http/README.md index 464be88b..4866d071 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -175,7 +175,7 @@ pub fn custom_authorizer_response(effect: &str, principal: &str, method_arn: &st } ``` -## Passing the Lambda execution context initialization to the handler +### Passing the Lambda execution context initialization to the handler One of the [best practices](https://docs.aws.amazon.com/lambda/latest/dg/best-practices.html) is to take advantage of execution environment reuse to improve the performance of your function. Initialize SDK clients and database connections outside the function handler. Subsequent invocations processed by the same instance of your function can reuse these resources. This saves cost by reducing function run time. @@ -229,3 +229,9 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: Ok(response) } ``` + +## Integration with API Gateway stages + +When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. + +If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is prevent, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. From 2aa9da4a126efd27efcdb423ebebe26cecba1683 Mon Sep 17 00:00:00 2001 From: Willem <1571851+wduminy@users.noreply.github.com> Date: Sat, 20 Jan 2024 21:22:26 +0200 Subject: [PATCH 069/211] ADD: axum middleware example (#785) --- examples/http-axum-middleware/Cargo.toml | 18 ++++++++++ examples/http-axum-middleware/README.md | 11 ++++++ examples/http-axum-middleware/src/main.rs | 43 +++++++++++++++++++++++ 3 files changed, 72 insertions(+) create mode 100644 examples/http-axum-middleware/Cargo.toml create mode 100644 examples/http-axum-middleware/README.md create mode 100644 examples/http-axum-middleware/src/main.rs diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml new file mode 100644 index 00000000..ea437052 --- /dev/null +++ b/examples/http-axum-middleware/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "http-axum-middleware" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_rest", +] } +lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "fmt", +] } + diff --git a/examples/http-axum-middleware/README.md b/examples/http-axum-middleware/README.md new file mode 100644 index 00000000..498f8a50 --- /dev/null +++ b/examples/http-axum-middleware/README.md @@ -0,0 +1,11 @@ +# AWS Lambda Function example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs new file mode 100644 index 00000000..41c5bf4b --- /dev/null +++ b/examples/http-axum-middleware/src/main.rs @@ -0,0 +1,43 @@ +//! This example demonstrates how [axum middleware](https://docs.rs/axum/latest/axum/middleware/index.html) +//! can be implemented. +//! +//! To test this: +//! ```sh +//! # start the local server +//! cargo lambda watch +//! # Then send through an example request +//! cargo lambda invoke --data-example apigw-request +//! ``` + +use axum::{response::Json, routing::post, Router}; +use lambda_http::request::RequestContext::ApiGatewayV1; +use lambda_http::{run, Error}; +use serde_json::{json, Value}; + +// Sample middleware that logs the request id +async fn mw_sample(req: axum::extract::Request, next: axum::middleware::Next) -> impl axum::response::IntoResponse { + let context = req.extensions().get::(); + if let Some(ApiGatewayV1(ctx)) = context { + tracing::info!("RequestId = {:?}", ctx.request_id); + } + next.run(req).await +} + +async fn handler_sample(body: Json) -> Json { + Json(json!({ "echo": *body })) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::INFO) + .with_target(false) + .without_time() + .init(); + + let app = Router::new() + .route("/testStage/hello/world", post(handler_sample)) + .route_layer(axum::middleware::from_fn(mw_sample)); + + run(app).await +} From b8880d23babacd32c5877b64e178eff0ed47781d Mon Sep 17 00:00:00 2001 From: Willem <1571851+wduminy@users.noreply.github.com> Date: Tue, 23 Jan 2024 05:40:09 +0200 Subject: [PATCH 070/211] Minor fixes to README.md (#787) --- lambda-http/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/README.md b/lambda-http/README.md index 4866d071..79c3410d 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -17,7 +17,7 @@ We are able to handle requests from: Thanks to the `Request` type we can seamlessly handle proxy integrations without the worry to specify the specific service type. -There is also an extension for `lambda_http::Request` structs that provide access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. +There is also an extension for `lambda_http::Request` structs that provides access to [API gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format) and [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) features. For example some handy extensions: @@ -40,7 +40,7 @@ Here you will find a few examples to handle basic scenarios: ### Reading a JSON from a body and deserialize into a structure -The code below creates a simple API Gateway proxy (HTTP, REST) that accept in input a JSON payload. +The code below creates a simple API Gateway proxy (HTTP, REST) that accepts in input a JSON payload. ```rust use lambda_http::{run, http::{StatusCode, Response}, service_fn, Error, IntoResponse, Request, RequestPayloadExt}; @@ -234,4 +234,4 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. -If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is prevent, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. +If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is present, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. From fde1d37ff678d520e85e3e1d035316e0266df394 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 23 Jan 2024 13:06:30 +0800 Subject: [PATCH 071/211] Tighten lambda-http event deserializer (#788) * Tighten lambda-http event deserializer * Add 'deny_unknown_fields' for alb request * Removed 'deny_unknown_fields' and make request_context mandatory for 'ApiGatewayProxyRequest' and 'ApiGatewayWebsocketProxyRequest' --- lambda-events/src/event/apigw/mod.rs | 9 ++--- .../src/fixtures/example-sqs-event.json | 3 +- lambda-http/src/deserializer.rs | 37 +++++++++++++++++-- 3 files changed, 38 insertions(+), 11 deletions(-) diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 36512f9c..f2cb097a 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -15,7 +15,7 @@ use std::collections::HashMap; #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequest where - T1: DeserializeOwned, + T1: DeserializeOwned + Default, T1: Serialize, { /// The resource path defined in API Gateway @@ -43,7 +43,6 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, - #[serde(default)] #[serde(bound = "")] pub request_context: ApiGatewayProxyRequestContext, #[serde(default)] @@ -335,9 +334,9 @@ pub struct ApiGatewayRequestIdentity { #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequest where - T1: DeserializeOwned, + T1: DeserializeOwned + Default, T1: Serialize, - T2: DeserializeOwned, + T2: DeserializeOwned + Default, T2: Serialize, { /// The resource path defined in API Gateway @@ -367,7 +366,7 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, - #[serde(bound = "", default)] + #[serde(bound = "")] pub request_context: ApiGatewayWebsocketProxyRequestContext, #[serde(default)] pub body: Option, diff --git a/lambda-events/src/fixtures/example-sqs-event.json b/lambda-events/src/fixtures/example-sqs-event.json index 732b65c1..eea77f84 100644 --- a/lambda-events/src/fixtures/example-sqs-event.json +++ b/lambda-events/src/fixtures/example-sqs-event.json @@ -37,5 +37,4 @@ } } ] -} - +} \ No newline at end of file diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 2584c0ad..1b2008c3 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -56,7 +56,7 @@ mod tests { fn test_deserialize_apigw_rest() { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-request.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw rest data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw rest data"); match req { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("12345678912", req.request_context.account_id.unwrap()); @@ -69,7 +69,7 @@ mod tests { fn test_deserialize_apigw_http() { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-v2-request-iam.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw http data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw http data"); match req { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); @@ -84,7 +84,7 @@ mod tests { "../../lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json" ); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze alb rest data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize alb rest data"); match req { LambdaRequest::Alb(req) => { assert_eq!( @@ -101,7 +101,7 @@ mod tests { let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json"); - let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialze apigw websocket data"); + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize apigw websocket data"); match req { LambdaRequest::WebSocket(req) => { assert_eq!("CONNECT", req.request_context.event_type.unwrap()); @@ -109,4 +109,33 @@ mod tests { other => panic!("unexpected request variant: {:?}", other), } } + + #[test] + #[cfg(feature = "pass_through")] + fn test_deserialize_bedrock_agent() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-bedrock-agent-runtime-event.json"); + + let req: LambdaRequest = + serde_json::from_slice(data).expect("failed to deserialize bedrock agent request data"); + match req { + LambdaRequest::PassThrough(req) => { + assert_eq!(String::from_utf8_lossy(data), req); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + #[cfg(feature = "pass_through")] + fn test_deserialize_sqs() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-sqs-event.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize sqs event data"); + match req { + LambdaRequest::PassThrough(req) => { + assert_eq!(String::from_utf8_lossy(data), req); + } + other => panic!("unexpected request variant: {:?}", other), + } + } } From b615b162900b8b01fb46568a40c9aa5c103d630d Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 24 Jan 2024 00:24:09 +0800 Subject: [PATCH 072/211] Release lambda-events 0.13.1 (#789) --- lambda-events/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 46ab7c11..894b236f 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.13.0" +version = "0.13.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", From baeaaec58aad8efd426119612ae69cffcef38b7b Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 24 Jan 2024 07:27:29 +0800 Subject: [PATCH 073/211] Release lambda-http 0.9.2 (#790) * Release lambda-http 0.9.2 * Update lambda-events version --- lambda-http/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4c84de21..ad48dff0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.1" +version = "0.9.2" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.13" +version = "0.13.1" default-features = false features = ["alb", "apigw"] From 0384c75cbf52772eb02fa83f29a440bb88554f89 Mon Sep 17 00:00:00 2001 From: Tom Keller <1083460+kellertk@users.noreply.github.com> Date: Wed, 24 Jan 2024 14:15:06 -0800 Subject: [PATCH 074/211] chore: a more polite closed issue message (#794) --- .github/workflows/closed-issue-message.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/closed-issue-message.yml b/.github/workflows/closed-issue-message.yml index f295d310..2a73fe92 100644 --- a/.github/workflows/closed-issue-message.yml +++ b/.github/workflows/closed-issue-message.yml @@ -11,7 +11,5 @@ jobs: # These inputs are both required repo-token: "${{ secrets.GITHUB_TOKEN }}" message: | - ### ⚠️COMMENT VISIBILITY WARNING⚠️ - Comments on closed issues are hard for the maintainers of this repository to see. - If you need more assistance, please open a new issue that references this one. - If you wish to keep having a conversation with other community members under this issue feel free to do so. + This issue is now closed. Comments on closed issues are hard for our team to see. + If you need more assistance, please either tag a team member or open a new issue that references this one. From 9f8f2500c99b45f50a4ea866851c484485bce5a2 Mon Sep 17 00:00:00 2001 From: Bryson M <43580701+Bryson14@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:27:00 -0500 Subject: [PATCH 075/211] Update axum documentation to include env var of stage name (#796) This would have been very helpful as I want making a lambda function that recieved paths from api gateway! --- examples/http-axum/src/main.rs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index c2805be1..cc3600a2 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,7 +6,8 @@ //! 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, @@ -35,8 +36,22 @@ async fn post_foo_name(Path(name): Path) -> Json { 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) { + let healthy = false; + match health { + true => {(StatusCode::OK, "Healthy!")}, + false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} + } +} + #[tokio::main] async fn main() -> Result<(), Error> { + // AWS Runtime can ignore Stage Name passed from json event + // 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) @@ -49,7 +64,8 @@ async fn main() -> Result<(), Error> { let app = Router::new() .route("/", get(root)) .route("/foo", get(get_foo).post(post_foo)) - .route("/foo/:name", post(post_foo_name)); + .route("/foo/:name", post(post_foo_name)) + .route("/health/", get(health_check)); run(app).await } From 42e4dfc1fe10e5f9e9310450296862914222b020 Mon Sep 17 00:00:00 2001 From: sn99 <42946112+sn99@users.noreply.github.com> Date: Sun, 28 Jan 2024 21:59:20 +0530 Subject: [PATCH 076/211] fix http-axum example code (#799) --- examples/http-axum/src/main.rs | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index cc3600a2..784d4bf1 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -1,24 +1,21 @@ -//! This is an example function that leverages the Lambda Rust runtime's HTTP support +//! This is an example function that leverages the Lambda Rust runtime HTTP support //! and the [axum](https://docs.rs/axum/latest/axum/index.html) web framework. The //! runtime HTTP support is backed by the [tower::Service](https://docs.rs/tower-service/0.3.2/tower_service/trait.Service.html) -//! trait. Axum applications are also backed by the `tower::Service` trait. That means +//! trait. Axum's applications are also backed by the `tower::Service` trait. That means //! that it is fairly easy to build an Axum application and pass the resulting `Service` //! 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 { Json(json!({ "msg": "I am GET /" })) @@ -36,12 +33,15 @@ async fn post_foo_name(Path(name): Path) -> Json { 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) { - let healthy = false; +/// Example on how to return status codes and data from an Axum function +async fn health_check() -> (StatusCode, String) { + let health = true; match health { - true => {(StatusCode::OK, "Healthy!")}, - false => {(StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!")} + true => (StatusCode::OK, "Healthy!".to_string()), + false => ( + StatusCode::INTERNAL_SERVER_ERROR, + "Not healthy!".to_string(), + ), } } @@ -51,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 2652b451401237964c623b75890898a2983b9881 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Mon, 29 Jan 2024 09:16:55 +0800 Subject: [PATCH 077/211] Support serialize UTF-8 String in HTTP Header Values (#800) * Support serialize UTF-8 String in HTTP Header Values * Fix issues in http-axum example * Revert "Fix issues in http-axum example" This reverts commit 77ad45a39fa8194bd3deec820ec795fa9a1f928a. * Add a unit test * Add a unit test to cover both 'serialize_headers' and 'serialize_multi_value_headers' --- lambda-events/src/custom_serde/headers.rs | 44 +++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 9cb89e40..2ef3050e 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() } @@ -187,4 +187,42 @@ 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_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_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 + }, + "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() + ); + } } From a837f3c79a4632b68924e448bd14a8f6b582743c Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Mon, 29 Jan 2024 10:16:51 +0000 Subject: [PATCH 078/211] Adds more tests to investigate #797 (#798) * Adds more tests to investigate https://github.com/awslabs/aws-lambda-rust-runtime/issues/797 * Update deserializer.rs --- .../example-apigw-sam-http-request.json | 52 ++++++++ .../example-apigw-sam-rest-request.json | 111 ++++++++++++++++++ lambda-http/src/deserializer.rs | 26 ++++ 3 files changed, 189 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-sam-http-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-sam-rest-request.json diff --git a/lambda-events/src/fixtures/example-apigw-sam-http-request.json b/lambda-events/src/fixtures/example-apigw-sam-http-request.json new file mode 100644 index 00000000..c60b05a9 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-sam-http-request.json @@ -0,0 +1,52 @@ +{ + "body": "", + "cookies": [], + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", + "Cache-Control": "no-cache", + "Connection": "keep-alive", + "Host": "127.0.0.1:3000", + "Pragma": "no-cache", + "Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"", + "Sec-Ch-Ua-Mobile": "?0", + "Sec-Ch-Ua-Platform": "\"macOS\"", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "X-Forwarded-Port": "3000", + "X-Forwarded-Proto": "http" + }, + "isBase64Encoded": false, + "pathParameters": {}, + "queryStringParameters": { + "foo": "bar" + }, + "rawPath": "/dump", + "rawQueryString": "foo=bar", + "requestContext": { + "accountId": "123456789012", + "apiId": "1234567890", + "domainName": "localhost", + "domainPrefix": "localhost", + "http": { + "method": "GET", + "path": "/dump", + "protocol": "HTTP/1.1", + "sourceIp": "127.0.0.1", + "userAgent": "Custom User Agent String" + }, + "requestId": "b3ea2f1a-9f2a-48a4-8791-0db24e5d40e5", + "routeKey": "GET /dump", + "stage": "$default", + "time": "28/Jan/2024:12:09:08 +0000", + "timeEpoch": 1706443748 + }, + "routeKey": "GET /dump", + "stageVariables": null, + "version": "2.0" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-sam-rest-request.json b/lambda-events/src/fixtures/example-apigw-sam-rest-request.json new file mode 100644 index 00000000..d125a7ab --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-sam-rest-request.json @@ -0,0 +1,111 @@ +{ + "body": null, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7", + "Accept-Encoding": "gzip, deflate, br", + "Accept-Language": "en-GB,en-US;q=0.9,en;q=0.8", + "Cache-Control": "max-age=0", + "Connection": "keep-alive", + "Host": "127.0.0.1:3000", + "Sec-Ch-Ua": "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"", + "Sec-Ch-Ua-Mobile": "?0", + "Sec-Ch-Ua-Platform": "\"macOS\"", + "Sec-Fetch-Dest": "document", + "Sec-Fetch-Mode": "navigate", + "Sec-Fetch-Site": "none", + "Sec-Fetch-User": "?1", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + "X-Forwarded-Port": "3000", + "X-Forwarded-Proto": "http" + }, + "httpMethod": "GET", + "isBase64Encoded": false, + "multiValueHeaders": { + "Accept": [ + "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7" + ], + "Accept-Encoding": [ + "gzip, deflate, br" + ], + "Accept-Language": [ + "en-GB,en-US;q=0.9,en;q=0.8" + ], + "Cache-Control": [ + "max-age=0" + ], + "Connection": [ + "keep-alive" + ], + "Host": [ + "127.0.0.1:3000" + ], + "Sec-Ch-Ua": [ + "\"Not_A Brand\";v=\"8\", \"Chromium\";v=\"120\", \"Google Chrome\";v=\"120\"" + ], + "Sec-Ch-Ua-Mobile": [ + "?0" + ], + "Sec-Ch-Ua-Platform": [ + "\"macOS\"" + ], + "Sec-Fetch-Dest": [ + "document" + ], + "Sec-Fetch-Mode": [ + "navigate" + ], + "Sec-Fetch-Site": [ + "none" + ], + "Sec-Fetch-User": [ + "?1" + ], + "Upgrade-Insecure-Requests": [ + "1" + ], + "User-Agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" + ], + "X-Forwarded-Port": [ + "3000" + ], + "X-Forwarded-Proto": [ + "http" + ] + }, + "multiValueQueryStringParameters": null, + "path": "/test", + "pathParameters": null, + "queryStringParameters": null, + "requestContext": { + "accountId": "123456789012", + "apiId": "1234567890", + "domainName": "127.0.0.1:3000", + "extendedRequestId": null, + "httpMethod": "GET", + "identity": { + "accountId": null, + "apiKey": null, + "caller": null, + "cognitoAuthenticationProvider": null, + "cognitoAuthenticationType": null, + "cognitoIdentityPoolId": null, + "sourceIp": "127.0.0.1", + "user": null, + "userAgent": "Custom User Agent String", + "userArn": null + }, + "path": "/test", + "protocol": "HTTP/1.1", + "requestId": "ea86b9eb-c688-4bbb-8309-a1671442bea9", + "requestTime": "28/Jan/2024:11:05:46 +0000", + "requestTimeEpoch": 1706439946, + "resourceId": "123456", + "resourcePath": "/test", + "stage": "Prod" + }, + "resource": "/test", + "stageVariables": null, + "version": "1.0" +} \ No newline at end of file diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 1b2008c3..4c0ad519 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -78,6 +78,32 @@ mod tests { } } + #[test] + fn test_deserialize_sam_rest() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-sam-rest-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize SAM rest data"); + match req { + LambdaRequest::ApiGatewayV1(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + + #[test] + fn test_deserialize_sam_http() { + let data = include_bytes!("../../lambda-events/src/fixtures/example-apigw-sam-http-request.json"); + + let req: LambdaRequest = serde_json::from_slice(data).expect("failed to deserialize SAM http data"); + match req { + LambdaRequest::ApiGatewayV2(req) => { + assert_eq!("123456789012", req.request_context.account_id.unwrap()); + } + other => panic!("unexpected request variant: {:?}", other), + } + } + #[test] fn test_deserialize_alb() { let data = include_bytes!( From 1face57dfef9576b67e73ab3b32c2ae573115a3f Mon Sep 17 00:00:00 2001 From: Ivan Kiselev <10528835+ivan-kiselev@users.noreply.github.com> Date: Tue, 30 Jan 2024 18:09:18 +0100 Subject: [PATCH 079/211] Make trigger reason for Cognito events Enum instead of String (#801) * Make trigger reason for cognito enums instead of strings * add module that tests each individual trigger source --- lambda-events/src/event/cognito/mod.rs | 305 +++++++++++++++++- ...cognito-event-userpools-custommessage.json | 31 +- ...-event-userpools-pretokengen-incoming.json | 9 +- ...ent-userpools-pretokengen-v2-incoming.json | 56 ++-- ...ognito-event-userpools-pretokengen-v2.json | 104 +++--- ...e-cognito-event-userpools-pretokengen.json | 15 +- 6 files changed, 404 insertions(+), 116 deletions(-) diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index decc31a5..95782ffc 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -44,11 +44,22 @@ pub struct CognitoDatasetRecord { pub struct CognitoEventUserPoolsPreSignup { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreSignupRequest, pub response: CognitoEventUserPoolsPreSignupResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreSignupTriggerSource { + #[serde(rename = "PreSignUp_SignUp")] + #[default] + SignUp, + #[serde(rename = "PreSignUp_AdminCreateUser")] + AdminCreateUser, + #[serde(rename = "PreSignUp_ExternalProvider")] + ExternalProvider, +} + /// `CognitoEventUserPoolsPreAuthentication` is sent by AWS Cognito User Pools when a user submits their information /// to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -56,11 +67,19 @@ pub struct CognitoEventUserPoolsPreSignup { pub struct CognitoEventUserPoolsPreAuthentication { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreAuthenticationRequest, pub response: CognitoEventUserPoolsPreAuthenticationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { + #[serde(rename = "PreAuthentication_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsPostConfirmation` is sent by AWS Cognito User Pools after a user is confirmed, /// allowing the Lambda to send custom messages or add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -68,11 +87,21 @@ pub struct CognitoEventUserPoolsPreAuthentication { pub struct CognitoEventUserPoolsPostConfirmation { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostConfirmationRequest, pub response: CognitoEventUserPoolsPostConfirmationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { + #[serde(rename = "PostConfirmation_ConfirmForgotPassword")] + ConfirmForgotPassword, + #[serde(rename = "PostConfirmation_ConfirmSignUp")] + #[default] + ConfirmSignUp, +} + /// `CognitoEventUserPoolsPreTokenGen` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -80,11 +109,26 @@ pub struct CognitoEventUserPoolsPostConfirmation { pub struct CognitoEventUserPoolsPreTokenGen { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequest, pub response: CognitoEventUserPoolsPreTokenGenResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { + #[serde(rename = "TokenGeneration_HostedAuth")] + HostedAuth, + #[serde(rename = "TokenGeneration_Authentication")] + #[default] + Authentication, + #[serde(rename = "TokenGeneration_NewPasswordChallenge")] + NewPasswordChallenge, + #[serde(rename = "TokenGeneration_AuthenticateDevice")] + AuthenticateDevice, + #[serde(rename = "TokenGeneration_RefreshTokens")] + RefreshTokens, +} + /// `CognitoEventUserPoolsPostAuthentication` is sent by AWS Cognito User Pools after a user is authenticated, /// allowing the Lambda to add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,11 +136,19 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub struct CognitoEventUserPoolsPostAuthentication { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostAuthenticationRequest, pub response: CognitoEventUserPoolsPostAuthenticationResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { + #[serde(rename = "PostAuthentication_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsMigrateUser` is sent by AWS Cognito User Pools when a user does not exist in the /// user pool at the time of sign-in with a password, or in the forgot-password flow. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -104,13 +156,22 @@ pub struct CognitoEventUserPoolsPostAuthentication { pub struct CognitoEventUserPoolsMigrateUser { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, #[serde(rename = "request")] pub cognito_event_user_pools_migrate_user_request: CognitoEventUserPoolsMigrateUserRequest, #[serde(rename = "response")] pub cognito_event_user_pools_migrate_user_response: CognitoEventUserPoolsMigrateUserResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsMigrateUserTriggerSource { + #[serde(rename = "UserMigration_Authentication")] + #[default] + Authentication, + #[serde(rename = "UserMigration_ForgotPassword")] + ForgotPassword, +} + /// `CognitoEventUserPoolsCallerContext` contains information about the caller #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -125,11 +186,11 @@ pub struct CognitoEventUserPoolsCallerContext { /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CognitoEventUserPoolsHeader { +pub struct CognitoEventUserPoolsHeader { #[serde(default)] pub version: Option, #[serde(default)] - pub trigger_source: Option, + pub trigger_source: Option, #[serde(default)] pub region: Option, #[serde(default)] @@ -220,7 +281,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { pub struct CognitoEventUserPoolsPreTokenGenV2 { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequestV2, pub response: CognitoEventUserPoolsPreTokenGenResponseV2, } @@ -384,11 +445,19 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { pub struct CognitoEventUserPoolsDefineAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { + #[serde(rename = "DefineAuthChallenge_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -426,11 +495,19 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { pub struct CognitoEventUserPoolsCreateAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { + #[serde(rename = "CreateAuthChallenge_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -469,11 +546,19 @@ pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { pub struct CognitoEventUserPoolsVerifyAuthChallenge { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: + CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { + #[serde(rename = "VerifyAuthChallengeResponse_Authentication")] + #[default] + Authentication, +} + /// `CognitoEventUserPoolsCustomMessage` is sent by AWS Cognito User Pools before a verification or MFA message is sent, /// allowing a user to customize the message dynamically. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -481,11 +566,30 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { pub struct CognitoEventUserPoolsCustomMessage { #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] - pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCustomMessageRequest, pub response: CognitoEventUserPoolsCustomMessageResponse, } +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] +pub enum CognitoEventUserPoolsCustomMessageTriggerSource { + #[serde(rename = "CustomMessage_SignUp")] + #[default] + SignUp, + #[serde(rename = "CustomMessage_AdminCreateUser")] + AdminCreateUser, + #[serde(rename = "CustomMessage_ResendCode")] + ResendCode, + #[serde(rename = "CustomMessage_ForgotPassword")] + ForgotPassword, + #[serde(rename = "CustomMessage_UpdateUserAttribute")] + UpdateUserAttribute, + #[serde(rename = "CustomMessage_VerifyUserAttribute")] + VerifyUserAttribute, + #[serde(rename = "CustomMessage_Authentication")] + Authentication, +} + /// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -736,3 +840,178 @@ mod test { assert_eq!(parsed, reparsed); } } + +#[cfg(test)] +#[cfg(feature = "cognito")] +mod trigger_source_tests { + use super::*; + + fn gen_header(trigger_source: &str) -> String { + format!( + r#" +{{ + "version": "1", + "triggerSource": "{trigger_source}", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": {{ + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }} +}}"# + ) + } + + #[test] + fn pre_sign_up() { + let possible_triggers = [ + "PreSignUp_AdminCreateUser", + "PreSignUp_AdminCreateUser", + "PreSignUp_ExternalProvider", + ]; + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + + #[test] + fn pre_authentication() { + let possible_triggers = ["PreAuthentication_Authentication"]; + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn post_confirmation() { + let possible_triggers = [ + "PostConfirmation_ConfirmForgotPassword", + "PostConfirmation_ConfirmSignUp", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn post_authentication() { + let possible_triggers = ["PostAuthentication_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn define_auth_challenge() { + let possible_triggers = ["DefineAuthChallenge_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + + #[test] + fn create_auth_challenge() { + let possible_triggers = ["CreateAuthChallenge_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn verify_auth_challenge() { + let possible_triggers = ["VerifyAuthChallengeResponse_Authentication"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn pre_token_generation() { + let possible_triggers = [ + "TokenGeneration_HostedAuth", + "TokenGeneration_Authentication", + "TokenGeneration_NewPasswordChallenge", + "TokenGeneration_AuthenticateDevice", + "TokenGeneration_RefreshTokens", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn user_migration() { + let possible_triggers = ["UserMigration_Authentication", "UserMigration_ForgotPassword"]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } + #[test] + fn custom_message() { + let possible_triggers = [ + "CustomMessage_SignUp", + "CustomMessage_AdminCreateUser", + "CustomMessage_ResendCode", + "CustomMessage_ForgotPassword", + "CustomMessage_UpdateUserAttribute", + "CustomMessage_VerifyUserAttribute", + "CustomMessage_Authentication", + ]; + + possible_triggers.into_iter().for_each(|trigger| { + let header = gen_header(trigger); + let parsed: CognitoEventUserPoolsHeader = + serde_json::from_str(&header).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsHeader<_> = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + }); + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json index 90e8b68e..8b2ca55d 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json @@ -1,28 +1,27 @@ { "version": "1", - "triggerSource": "CustomMessage_SignUp/CustomMessage_ResendCode/CustomMessage_ForgotPassword/CustomMessage_VerifyUserAttribute", + "triggerSource": "CustomMessage_VerifyUserAttribute", "region": "", "userPoolId": "", "userName": "", "callerContext": { - "awsSdkVersion": "", - "clientId": "" + "awsSdkVersion": "", + "clientId": "" }, "request": { - "userAttributes": { - "phone_number_verified": true, - "email_verified": false - }, - "codeParameter": "####", - "usernameParameter": "{username}", - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } + "userAttributes": { + "phone_number_verified": true, + "email_verified": false + }, + "codeParameter": "####", + "usernameParameter": "{username}", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } }, "response": { - "smsMessage": "", - "emailMessage": "", - "emailSubject": "" + "smsMessage": "", + "emailMessage": "", + "emailSubject": "" } } - diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json index fed10a51..ae78b7c7 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json @@ -1,6 +1,6 @@ { "version": "1", - "triggerSource": "PreTokenGen", + "triggerSource": "TokenGeneration_Authentication", "region": "region", "userPoolId": "userPoolId", "userName": "userName", @@ -15,7 +15,11 @@ }, "groupConfiguration": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, "clientMetadata": { @@ -26,4 +30,3 @@ "claimsOverrideDetails": null } } - diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json index 3376d6e0..e5c776d8 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2-incoming.json @@ -1,33 +1,33 @@ { - "version": "1", - "triggerSource": "PreTokenGen", - "region": "region", - "userPoolId": "userPoolId", - "userName": "userName", - "callerContext": { - "awsSdkVersion": "calling aws sdk with version", - "clientId": "apps client id" + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" }, - "request": { - "userAttributes": { - "email": "email", - "phone_number": "phone_number" - }, - "scopes": ["scope-1", "scope-2"], - "groupConfiguration": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" - }, - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, - "response": { - "claimsOverrideDetails": null + "clientMetadata": { + "exampleMetadataKey": "example metadata value" } + }, + "response": { + "claimsOverrideDetails": null + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json index f7ccfe2f..6046d446 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-v2.json @@ -1,58 +1,58 @@ { - "version": "1", - "triggerSource": "PreTokenGen", - "region": "region", - "userPoolId": "userPoolId", - "userName": "userName", - "callerContext": { - "awsSdkVersion": "calling aws sdk with version", - "clientId": "apps client id" + "version": "1", + "triggerSource": "TokenGeneration_HostedAuth", + "region": "region", + "userPoolId": "userPoolId", + "userName": "userName", + "callerContext": { + "awsSdkVersion": "calling aws sdk with version", + "clientId": "apps client id" + }, + "request": { + "userAttributes": { + "email": "email", + "phone_number": "phone_number" }, - "request": { - "userAttributes": { - "email": "email", - "phone_number": "phone_number" + "scopes": ["scope-1", "scope-2"], + "groupConfiguration": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "claimsAndScopeOverrideDetails": { + "idTokenGeneration": { + "claimsToAddOrOverride": { + "string": "string" }, - "scopes": ["scope-1", "scope-2"], - "groupConfiguration": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + "claimsToSuppress": ["string", "string"] + }, + "accessTokenGeneration": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" }, - "clientMetadata": { - "exampleMetadataKey": "example metadata value" - } - }, - "response": { - "claimsAndScopeOverrideDetails": { - "idTokenGeneration": { - "claimsToAddOrOverride": { - "string": "string" - }, - "claimsToSuppress": ["string", "string"] - }, - "accessTokenGeneration": { - "claimsToAddOrOverride": { - "attribute_key2": "attribute_value2", - "attribute_key": "attribute_value" - }, - "claimsToSuppress": ["email", "phone"], - "scopesToAdd": ["scope-B", "scope-B"], - "scopesToSuppress": ["scope-C", "scope-D"] - }, - "groupOverrideDetails": { - "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": [ - "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", - "arn:aws:iam::XXXXXXXXX:role/sns_callerB", - "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" - ], - "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" - } - } + "claimsToSuppress": ["email", "phone"], + "scopesToAdd": ["scope-B", "scope-B"], + "scopesToSuppress": ["scope-C", "scope-D"] + }, + "groupOverrideDetails": { + "groupsToOverride": ["group-A", "group-B", "group-C"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } } + } } diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json index 7b851904..f79e8573 100644 --- a/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json @@ -1,6 +1,6 @@ { "version": "1", - "triggerSource": "PreTokenGen", + "triggerSource": "TokenGeneration_HostedAuth", "region": "region", "userPoolId": "userPoolId", "userName": "userName", @@ -15,7 +15,11 @@ }, "groupConfiguration": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" }, "clientMetadata": { @@ -31,10 +35,13 @@ "claimsToSuppress": ["email"], "groupOverrideDetails": { "groupsToOverride": ["group-A", "group-B", "group-C"], - "iamRolesToOverride": ["arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", "arn:aws:iam::XXXXXXXXX:role/sns_callerB", "arn:aws:iam::XXXXXXXXXX:role/sns_callerC"], + "iamRolesToOverride": [ + "arn:aws:iam::XXXXXXXXXXXX:role/sns_callerA", + "arn:aws:iam::XXXXXXXXX:role/sns_callerB", + "arn:aws:iam::XXXXXXXXXX:role/sns_callerC" + ], "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" } } } } - From e79183bc03478b1fa98e02625844357d4f319705 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 31 Jan 2024 07:57:06 -0800 Subject: [PATCH 080/211] Add assertions about multi value query parameters. (#803) * Add assertions about multi value query parameters. Signed-off-by: David Calavera * Add test for Axum Query extractor. Use the axum_extra package as it seems to be more correct: https://github.com/tokio-rs/axum/blob/main/axum-extra/src/extract/query.rs Signed-off-by: David Calavera * Update MSRV because Axum doesn't support our MSRV. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-http/Cargo.toml | 2 + lambda-http/src/request.rs | 99 +++++++++++++++-- .../tests/data/alb_multi_value_request.json | 103 ++++++++++++------ .../data/apigw_multi_value_proxy_request.json | 48 ++++---- 8 files changed, 185 insertions(+), 75 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index caee1131..d9f5c72a 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 7165a281..d9bcc989 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 026c20e0..25cd83ec 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.65.0" # Current MSRV + - "1.66.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index eeeaf2fa..43c38586 100644 --- a/README.md +++ b/README.md @@ -440,7 +440,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.65, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.66, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ad48dff0..1b9cd693 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -50,6 +50,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] +axum-core = "0.4.3" +axum-extra = { version = "0.9.2", features = ["query"] } lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index c98f5c17..bce2e3d3 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -570,6 +570,9 @@ mod tests { matches!(req_context, &RequestContext::ApiGatewayV2(_)), "expected ApiGatewayV2 context, got {req_context:?}" ); + + let (parts, _) = req.into_parts(); + assert_eq!("https://id.execute-api.us-east-1.amazonaws.com/my/path?parameter1=value1¶meter1=value2¶meter2=value", parts.uri.to_string()); } #[test] @@ -699,12 +702,16 @@ mod tests { .is_empty()); // test RequestExt#query_string_parameters_ref does the right thing - assert_eq!( - request - .query_string_parameters_ref() - .and_then(|params| params.all("multivalueName")), - Some(vec!["you", "me"]) - ); + let params = request.query_string_parameters(); + assert_eq!(Some(vec!["you", "me"]), params.all("multiValueName")); + assert_eq!(Some(vec!["me"]), params.all("name")); + + let query = request.uri().query().unwrap(); + assert!(query.contains("name=me")); + assert!(query.contains("multiValueName=you&multiValueName=me")); + let (parts, _) = request.into_parts(); + assert!(parts.uri.to_string().contains("name=me")); + assert!(parts.uri.to_string().contains("multiValueName=you&multiValueName=me")); } #[test] @@ -724,12 +731,13 @@ mod tests { .is_empty()); // test RequestExt#query_string_parameters_ref does the right thing - assert_eq!( - request - .query_string_parameters_ref() - .and_then(|params| params.all("myKey")), - Some(vec!["val1", "val2"]) - ); + let params = request.query_string_parameters(); + assert_eq!(Some(vec!["val1", "val2"]), params.all("myKey")); + assert_eq!(Some(vec!["val3", "val4"]), params.all("myOtherKey")); + + let query = request.uri().query().unwrap(); + assert!(query.contains("myKey=val1&myKey=val2")); + assert!(query.contains("myOtherKey=val3&myOtherKey=val4")); } #[test] @@ -845,4 +853,71 @@ mod tests { assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/Prod/path")); assert_eq!("/Prod/path", apigw_path_with_stage(&Some("Prod".into()), "/path")); } + + #[tokio::test] + #[cfg(feature = "apigw_rest")] + async fn test_axum_query_extractor_apigw_rest() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + // from docs + // https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format + let input = include_str!("../tests/data/apigw_multi_value_proxy_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Params { + name: Vec, + multi_value_name: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["me"], query.0.name); + assert_eq!(vec!["you", "me"], query.0.multi_value_name); + } + + #[tokio::test] + #[cfg(feature = "apigw_http")] + async fn test_axum_query_extractor_apigw_http() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + let input = include_str!("../tests/data/apigw_v2_proxy_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + struct Params { + parameter1: Vec, + parameter2: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["value1", "value2"], query.0.parameter1); + assert_eq!(vec!["value"], query.0.parameter2); + } + + #[tokio::test] + #[cfg(feature = "alb")] + async fn test_axum_query_extractor_alb() { + use axum_core::extract::FromRequestParts; + use axum_extra::extract::Query; + let input = include_str!("../tests/data/alb_multi_value_request.json"); + let request = from_str(input).expect("failed to parse request"); + let (mut parts, _) = request.into_parts(); + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct Params { + my_key: Vec, + my_other_key: Vec, + } + struct State; + + let query = Query::::from_request_parts(&mut parts, &State).await.unwrap(); + assert_eq!(vec!["val1", "val2"], query.0.my_key); + assert_eq!(vec!["val3", "val4"], query.0.my_other_key); + } } diff --git a/lambda-http/tests/data/alb_multi_value_request.json b/lambda-http/tests/data/alb_multi_value_request.json index 10cf1155..49edfd23 100644 --- a/lambda-http/tests/data/alb_multi_value_request.json +++ b/lambda-http/tests/data/alb_multi_value_request.json @@ -1,37 +1,70 @@ { - "requestContext": { - "elb": { - "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" - } - }, - "httpMethod": "GET", - "path": "/", - "queryStringParameters": { "myKey": "val2" }, - "multiValueQueryStringParameters": { "myKey": ["val1", "val2"] }, - "headers": { - "accept": "text/html,application/xhtml+xml", - "accept-language": "en-US,en;q=0.8", - "content-type": "text/plain", - "cookie": "name1=value1", - "host": "lambda-846800462-us-east-2.elb.amazonaws.com", - "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", - "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", - "x-forwarded-for": "72.21.198.66", - "x-forwarded-port": "443", - "x-forwarded-proto": "https" - }, - "multiValueHeaders": { - "accept": ["text/html,application/xhtml+xml"], - "accept-language": ["en-US,en;q=0.8"], - "content-type": ["text/plain"], - "cookie": ["name1=value1", "name2=value2"], - "host": ["lambda-846800462-us-east-2.elb.amazonaws.com"], - "user-agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)"], - "x-amzn-trace-id": ["Root=1-5bdb40ca-556d8b0c50dc66f0511bf520"], - "x-forwarded-for": ["72.21.198.66"], - "x-forwarded-port": ["443"], - "x-forwarded-proto": ["https"] - }, - "isBase64Encoded": false, - "body": "request_body" + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:region:123456789012:targetgroup/my-target-group/6d0ecf831eec9f09" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { + "myKey": "val2", + "myOtherKey": "val3" + }, + "multiValueQueryStringParameters": { + "myKey": [ + "val1", + "val2" + ], + "myOtherKey": [ + "val3", + "val4" + ] + }, + "headers": { + "accept": "text/html,application/xhtml+xml", + "accept-language": "en-US,en;q=0.8", + "content-type": "text/plain", + "cookie": "name1=value1", + "host": "lambda-846800462-us-east-2.elb.amazonaws.com", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)", + "x-amzn-trace-id": "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520", + "x-forwarded-for": "72.21.198.66", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "multiValueHeaders": { + "accept": [ + "text/html,application/xhtml+xml" + ], + "accept-language": [ + "en-US,en;q=0.8" + ], + "content-type": [ + "text/plain" + ], + "cookie": [ + "name1=value1", + "name2=value2" + ], + "host": [ + "lambda-846800462-us-east-2.elb.amazonaws.com" + ], + "user-agent": [ + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6)" + ], + "x-amzn-trace-id": [ + "Root=1-5bdb40ca-556d8b0c50dc66f0511bf520" + ], + "x-forwarded-for": [ + "72.21.198.66" + ], + "x-forwarded-port": [ + "443" + ], + "x-forwarded-proto": [ + "https" + ] + }, + "isBase64Encoded": false, + "body": "request_body" } \ No newline at end of file diff --git a/lambda-http/tests/data/apigw_multi_value_proxy_request.json b/lambda-http/tests/data/apigw_multi_value_proxy_request.json index 8f84aeb9..280f49cb 100644 --- a/lambda-http/tests/data/apigw_multi_value_proxy_request.json +++ b/lambda-http/tests/data/apigw_multi_value_proxy_request.json @@ -23,74 +23,74 @@ "X-Forwarded-Port": "443", "X-Forwarded-Proto": "https" }, - "multiValueHeaders":{ - "Accept":[ + "multiValueHeaders": { + "Accept": [ "*/*" ], - "Accept-Encoding":[ + "Accept-Encoding": [ "gzip, deflate" ], - "cache-control":[ + "cache-control": [ "no-cache" ], - "CloudFront-Forwarded-Proto":[ + "CloudFront-Forwarded-Proto": [ "https" ], - "CloudFront-Is-Desktop-Viewer":[ + "CloudFront-Is-Desktop-Viewer": [ "true" ], - "CloudFront-Is-Mobile-Viewer":[ + "CloudFront-Is-Mobile-Viewer": [ "false" ], - "CloudFront-Is-SmartTV-Viewer":[ + "CloudFront-Is-SmartTV-Viewer": [ "false" ], - "CloudFront-Is-Tablet-Viewer":[ + "CloudFront-Is-Tablet-Viewer": [ "false" ], - "CloudFront-Viewer-Country":[ + "CloudFront-Viewer-Country": [ "US" ], - "Content-Type":[ + "Content-Type": [ "application/json" ], - "headerName":[ + "headerName": [ "headerValue" ], - "Host":[ + "Host": [ "gy415nuibc.execute-api.us-east-1.amazonaws.com" ], - "Postman-Token":[ + "Postman-Token": [ "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" ], - "User-Agent":[ + "User-Agent": [ "PostmanRuntime/2.4.5" ], - "Via":[ + "Via": [ "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" ], - "X-Amz-Cf-Id":[ + "X-Amz-Cf-Id": [ "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" ], - "X-Forwarded-For":[ + "X-Forwarded-For": [ "54.240.196.186, 54.182.214.83" ], - "X-Forwarded-Port":[ + "X-Forwarded-Port": [ "443" ], - "X-Forwarded-Proto":[ + "X-Forwarded-Proto": [ "https" ] }, "queryStringParameters": { "name": "me", - "multivalueName": "me" + "multiValueName": "me" }, - "multiValueQueryStringParameters":{ - "name":[ + "multiValueQueryStringParameters": { + "name": [ "me" ], - "multivalueName":[ + "multiValueName": [ "you", "me" ] From 4824d24c2d3fe2d3faefee326f84cc52c91814ac Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 31 Jan 2024 18:36:43 -0800 Subject: [PATCH 081/211] Add example route for Axum query extractors. (#804) This helps us verify the query integration with Axum. Signed-off-by: David Calavera --- examples/http-axum/Cargo.toml | 1 + examples/http-axum/src/main.rs | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 88df4140..5257bdb0 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -14,6 +14,7 @@ edition = "2021" axum = "0.7" lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } tracing = { version = "0.1", features = ["log"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index 784d4bf1..ae7b0921 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,6 +6,7 @@ //! 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 axum::extract::Query; use axum::http::StatusCode; use axum::{ extract::Path, @@ -14,9 +15,16 @@ use axum::{ Router, }; use lambda_http::{run, Error}; +use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::env::set_var; +#[derive(Deserialize, Serialize)] +struct Params { + first: Option, + second: Option, +} + async fn root() -> Json { Json(json!({ "msg": "I am GET /" })) } @@ -33,22 +41,26 @@ async fn post_foo_name(Path(name): Path) -> Json { Json(json!({ "msg": format!("I am POST /foo/:name, name={name}") })) } +async fn get_parameters(Query(params): Query) -> Json { + Json(json!({ "request parameters": params })) +} + /// Example on how to return status codes and data from an Axum function async fn health_check() -> (StatusCode, String) { let health = true; match health { true => (StatusCode::OK, "Healthy!".to_string()), - false => ( - StatusCode::INTERNAL_SERVER_ERROR, - "Not healthy!".to_string(), - ), + false => (StatusCode::INTERNAL_SERVER_ERROR, "Not healthy!".to_string()), } } #[tokio::main] async fn main() -> Result<(), Error> { - // AWS Runtime can ignore Stage Name passed from json event - // Remove if you want the first section of the url to be the stage name of the API Gateway + // If you use API Gateway stages, the Rust Runtime will include the stage name + // as part of the path that your application receives. + // Setting the following environment variable, you can remove the stage from the path. + // This variable only applies to API Gateway stages, + // you can remove it if you don't use them. // i.e with: `GET /test-stage/todo/id/123` without: `GET /todo/id/123` set_var("AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH", "true"); @@ -65,6 +77,7 @@ async fn main() -> Result<(), Error> { .route("/", get(root)) .route("/foo", get(get_foo).post(post_foo)) .route("/foo/:name", post(post_foo_name)) + .route("/parameters", get(get_parameters)) .route("/health/", get(health_check)); run(app).await From 11d5669542d38ba2e1b5aa2d64b8e445d93718ea Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 5 Feb 2024 18:34:43 -0800 Subject: [PATCH 082/211] Run tests on examples (#810) Some examples include tests, run them to verify that they are correct. --- examples/check-examples.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/check-examples.sh b/examples/check-examples.sh index f55bced9..36e3038e 100755 --- a/examples/check-examples.sh +++ b/examples/check-examples.sh @@ -10,7 +10,7 @@ for f in *; do if [ -d "$f" ]; then echo "==> Checking example: $f" cd $f - cargo check + cargo test cd .. fi done From 9ed543c2af5cbe728d91c7d1f2ca7895152b0ff8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 6 Feb 2024 09:22:59 -0800 Subject: [PATCH 083/211] Add CDK stack example (#807) Signed-off-by: David Calavera --- examples/http-axum/cdk/.gitignore | 8 + examples/http-axum/cdk/.npmignore | 6 + examples/http-axum/cdk/README.md | 40 + examples/http-axum/cdk/bin/cdk.ts | 21 + examples/http-axum/cdk/cdk.json | 65 + examples/http-axum/cdk/lib/cdk-stack.ts | 26 + examples/http-axum/cdk/package-lock.json | 7632 ++++++++++++++++++++++ examples/http-axum/cdk/package.json | 28 + examples/http-axum/cdk/tsconfig.json | 31 + 9 files changed, 7857 insertions(+) create mode 100644 examples/http-axum/cdk/.gitignore create mode 100644 examples/http-axum/cdk/.npmignore create mode 100644 examples/http-axum/cdk/README.md create mode 100644 examples/http-axum/cdk/bin/cdk.ts create mode 100644 examples/http-axum/cdk/cdk.json create mode 100644 examples/http-axum/cdk/lib/cdk-stack.ts create mode 100644 examples/http-axum/cdk/package-lock.json create mode 100644 examples/http-axum/cdk/package.json create mode 100644 examples/http-axum/cdk/tsconfig.json diff --git a/examples/http-axum/cdk/.gitignore b/examples/http-axum/cdk/.gitignore new file mode 100644 index 00000000..f60797b6 --- /dev/null +++ b/examples/http-axum/cdk/.gitignore @@ -0,0 +1,8 @@ +*.js +!jest.config.js +*.d.ts +node_modules + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/http-axum/cdk/.npmignore b/examples/http-axum/cdk/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/examples/http-axum/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/http-axum/cdk/README.md b/examples/http-axum/cdk/README.md new file mode 100644 index 00000000..902f576e --- /dev/null +++ b/examples/http-axum/cdk/README.md @@ -0,0 +1,40 @@ +# Axum HTTP CDK Stack + +This is a basic stack that shows how to deploy the Axum HTTP example with the AWS CDK. + +## Resources + +This stack deploys the Axum HTTP example in AWS Lambda. + +It also creates an API Gateway Rest API to expose the Axum app to the internet. When the deploy is completed, the stack will print the endpoint URL for the gateway. It will look something like this: + +``` +CdkStack.axumEndpointC1B330D3 = https://sr0e4dqg1b.execute-api.us-east-1.amazonaws.com/prod/ +``` + +If you set the environment variable `ENABLE_LAMBDA_RUST_AXUM_FUNCTION_URL=true` in your terminal before deploying the stack, it will also create a Lambda Function URL without any authentication mode. When the deploy completes, the stack will print the endpoint URL for this function. It will look something like this: + +``` +CdkStack.AxumFunctionUrl = https://7st53uq3rpk4jweki2ek765gty0icvuf.lambda-url.us-east-1.on.aws/ +``` + +## Dependencies + +1. Install the AWS CDK with NPM: `npm install -g cdk`. +2. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) + +## Deployment + +Run `npm run build` to complile the stack. + +Then, run `npm run cdk deploy` to deploy the stack on your AWS account. + +## Security + +This example doesn't provide any security configuration. It's up to you to configure the stack with the security settings that are more convenient to you. We're not responsible for resources open to the internet on your AWS account. + +## Cleanup + +Deploying this stack on your account might incur on AWS costs due to the resources that we're deploying. Don't forget to delete those resources from your account if you're not using them any longer. + +Run `npm run cdk destroy` to delete all resources in this stack from your AWS account. diff --git a/examples/http-axum/cdk/bin/cdk.ts b/examples/http-axum/cdk/bin/cdk.ts new file mode 100644 index 00000000..1d8bfd97 --- /dev/null +++ b/examples/http-axum/cdk/bin/cdk.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { CdkStack } from '../lib/cdk-stack'; + +const app = new cdk.App(); +new CdkStack(app, 'CdkStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/examples/http-axum/cdk/cdk.json b/examples/http-axum/cdk/cdk.json new file mode 100644 index 00000000..7dc211da --- /dev/null +++ b/examples/http-axum/cdk/cdk.json @@ -0,0 +1,65 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true + } +} diff --git a/examples/http-axum/cdk/lib/cdk-stack.ts b/examples/http-axum/cdk/lib/cdk-stack.ts new file mode 100644 index 00000000..a0cc27a6 --- /dev/null +++ b/examples/http-axum/cdk/lib/cdk-stack.ts @@ -0,0 +1,26 @@ +import { join } from 'path'; +import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import { RustFunction } from 'cargo-lambda-cdk'; +import { LambdaRestApi } from 'aws-cdk-lib/aws-apigateway' +import { FunctionUrlAuthType } from "aws-cdk-lib/aws-lambda"; + +export class CdkStack extends Stack { + constructor(scope: Construct, id: string, props?: StackProps) { + super(scope, id, props); + + const handler = new RustFunction(this, 'Axum API', { + // Path to the http-axum root directory. + manifestPath: join(__dirname, '..', '..'), + }); + + if (process.env.ENABLE_LAMBDA_RUST_AXUM_FUNCTION_URL) { + const lambdaUrl = handler.addFunctionUrl({ + authType: FunctionUrlAuthType.NONE, + }); + new CfnOutput(this, 'Axum FunctionUrl ', { value: lambdaUrl.url }); + } + + new LambdaRestApi(this, 'axum', { handler }); + } +} diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json new file mode 100644 index 00000000..c35c0bf4 --- /dev/null +++ b/examples/http-axum/cdk/package-lock.json @@ -0,0 +1,7632 @@ +{ + "name": "cdk", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cdk", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "^2.126.0", + "cargo-lambda-cdk": "^0.0.17", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "cdk": "bin/cdk.js" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "20.11.14", + "aws-cdk": "2.126.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "~5.3.3" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", + "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/code-frame/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/code-frame/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/code-frame/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/code-frame/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/code-frame/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.11.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz", + "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aws-cdk": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.126.0.tgz", + "integrity": "sha512-hEyy8UCEEUnkieH6JbJBN8XAbvuVZNdBmVQ8wHCqo8RSNqmpwM1qvLiyXV/2JvCqJJ0bl9uBiZ98Ytd5i3wW7g==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", + "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.5.4", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.12.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/lru-cache": { + "version": "6.0.0", + "inBundle": true, + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.5.4", + "inBundle": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.1", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "inBundle": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yallist": { + "version": "4.0.0", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001583", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", + "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cargo-lambda-cdk": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.17.tgz", + "integrity": "sha512-KV95oV4GdczNioji8EvHbYumsArdGRusXsNhfRSBIoE94WN8jlxySaUzboNiH+HEKshyUQtnvdQd/WLBdk5NDA==", + "bundleDependencies": [ + "js-toml" + ], + "dependencies": { + "js-toml": "^0.1.1" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.1.0", + "constructs": "^10.0.5" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@babel/runtime-corejs3": { + "version": "7.22.15", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/gast": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/types": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/utils": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/chevrotain": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/core-js-pure": { + "version": "3.32.2", + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/js-toml": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/lodash": { + "version": "4.17.21", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regenerator-runtime": { + "version": "0.14.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regexp-to-ast": { + "version": "0.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/xregexp": { + "version": "5.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.656", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", + "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-snapshot/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "dependencies": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-jest/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.202", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", + "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" + }, + "@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", + "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + }, + "@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "dev": true + }, + "@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dev": true, + "requires": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "dev": true + }, + "@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dev": true, + "requires": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.15" + } + }, + "@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dev": true, + "requires": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "requires": { + "@babel/types": "^7.22.5" + } + }, + "@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "dev": true + }, + "@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dev": true, + "requires": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "dev": true + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.23.3.tgz", + "integrity": "sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.22.5" + } + }, + "@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + } + }, + "@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "requires": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + } + }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + } + }, + "@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/node": { + "version": "20.11.14", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.14.tgz", + "integrity": "sha512-w3yWCcwULefjP9DmDDsgUskrMoOy5Z8MiwKHr1FvqGPtx7CvJzQvxD7eKpxNtklQxLruxSXWddyeRtyud0RcXQ==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", + "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aws-cdk": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.126.0.tgz", + "integrity": "sha512-hEyy8UCEEUnkieH6JbJBN8XAbvuVZNdBmVQ8wHCqo8RSNqmpwM1qvLiyXV/2JvCqJJ0bl9uBiZ98Ytd5i3wW7g==", + "dev": true, + "requires": { + "fsevents": "2.3.2" + } + }, + "aws-cdk-lib": { + "version": "2.126.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", + "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.0", + "jsonschema": "^1.4.1", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.5.4", + "table": "^6.8.1", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true + }, + "ajv": { + "version": "8.12.0", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fs-extra": { + "version": "11.2.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "ignore": { + "version": "5.3.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "bundled": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "lru-cache": { + "version": "6.0.0", + "bundled": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", + "bundled": true + }, + "semver": { + "version": "7.5.4", + "bundled": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "slice-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.8.1", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "bundled": true + }, + "uri-js": { + "version": "4.4.1", + "bundled": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "yallist": { + "version": "4.0.0", + "bundled": true + }, + "yaml": { + "version": "1.10.2", + "bundled": true + } + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001583", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001583.tgz", + "integrity": "sha512-acWTYaha8xfhA/Du/z4sNZjHUWjkiuoAi2LM+T/aL+kemKQgPT1xBb/YKjlQ0Qo8gvbHsGNplrEJ+9G3gL7i4Q==", + "dev": true + }, + "cargo-lambda-cdk": { + "version": "0.0.17", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.17.tgz", + "integrity": "sha512-KV95oV4GdczNioji8EvHbYumsArdGRusXsNhfRSBIoE94WN8jlxySaUzboNiH+HEKshyUQtnvdQd/WLBdk5NDA==", + "requires": { + "js-toml": "^0.1.1" + }, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.22.15", + "bundled": true, + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } + }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "bundled": true + }, + "@chevrotain/utils": { + "version": "10.5.0", + "bundled": true + }, + "chevrotain": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "core-js-pure": { + "version": "3.32.2", + "bundled": true + }, + "js-toml": { + "version": "0.1.1", + "bundled": true, + "requires": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "lodash": { + "version": "4.17.21", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.14.0", + "bundled": true + }, + "regexp-to-ast": { + "version": "0.5.0", + "bundled": true + }, + "xregexp": { + "version": "5.1.1", + "bundled": true, + "requires": { + "@babel/runtime-corejs3": "^7.16.5" + } + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "dedent": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.1.tgz", + "integrity": "sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==", + "dev": true, + "requires": {} + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.656", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.656.tgz", + "integrity": "sha512-9AQB5eFTHyR3Gvt2t/NwR0le2jBSUNwCnMbUCejFWHD+so4tH40/dRLgoE+jxlPeWS43XJewyvCv+I8LPMl49Q==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dev": true, + "requires": { + "hasown": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.6.tgz", + "integrity": "sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + } + }, + "jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "requires": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + } + }, + "jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "pure-rand": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", + "dev": true + }, + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "29.1.2", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.2.tgz", + "integrity": "sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g==", + "dev": true, + "requires": { + "bs-logger": "0.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "4.x", + "make-error": "1.x", + "semver": "^7.5.3", + "yargs-parser": "^21.0.1" + }, + "dependencies": { + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz", + "integrity": "sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json new file mode 100644 index 00000000..9117d19f --- /dev/null +++ b/examples/http-axum/cdk/package.json @@ -0,0 +1,28 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.11", + "@types/node": "20.11.14", + "aws-cdk": "2.126.0", + "jest": "^29.7.0", + "ts-jest": "^29.1.2", + "ts-node": "^10.9.2", + "typescript": "~5.3.3" + }, + "dependencies": { + "aws-cdk-lib": "^2.126.0", + "cargo-lambda-cdk": "^0.0.17", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} diff --git a/examples/http-axum/cdk/tsconfig.json b/examples/http-axum/cdk/tsconfig.json new file mode 100644 index 00000000..aaa7dc51 --- /dev/null +++ b/examples/http-axum/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} From 94b33e864e94a1dde1462a9ad4bc72d4213a00b1 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Thu, 8 Feb 2024 12:10:59 +0900 Subject: [PATCH 084/211] Add SecretsManagerSecretRotationEvent (#811) --- Makefile | 1 + lambda-events/Cargo.toml | 2 ++ lambda-events/src/event/mod.rs | 4 ++++ lambda-events/src/event/secretsmanager/mod.rs | 24 +++++++++++++++++++ ...-secretsmanager-secret-rotation-event.json | 5 ++++ lambda-events/src/lib.rs | 4 ++++ 6 files changed, 40 insertions(+) create mode 100644 lambda-events/src/event/secretsmanager/mod.rs create mode 100644 lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json diff --git a/Makefile b/Makefile index f66ee1fd..ecfd7623 100644 --- a/Makefile +++ b/Makefile @@ -101,6 +101,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features rabbitmq cargo test --package aws_lambda_events --no-default-features --features s3 cargo test --package aws_lambda_events --no-default-features --features s3_batch_job + cargo test --package aws_lambda_events --no-default-features --features secretsmanager cargo test --package aws_lambda_events --no-default-features --features ses cargo test --package aws_lambda_events --no-default-features --features sns cargo test --package aws_lambda_events --no-default-features --features sqs diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 894b236f..c6556b64 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -75,6 +75,7 @@ default = [ "s3", "s3_batch_job", "ses", + "secretsmanager", "sns", "sqs", "streams", @@ -119,6 +120,7 @@ lex = [] rabbitmq = [] s3 = ["bytes", "chrono", "http", "http-body", "http-serde"] s3_batch_job = ["s3"] +secretsmanager = [] ses = ["chrono"] sns = ["chrono", "serde_with"] sqs = ["serde_with"] diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index ea0ca7df..d63acc4d 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -133,6 +133,10 @@ pub mod rabbitmq; #[cfg(feature = "s3")] pub mod s3; +/// AWS Lambda event definitions for secretsmanager. +#[cfg(feature = "secretsmanager")] +pub mod secretsmanager; + /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] pub mod ses; diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs new file mode 100644 index 00000000..3ed8d238 --- /dev/null +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -0,0 +1,24 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SecretsManagerSecretRotationEvent { + pub step: String, + pub secret_id: String, + pub client_request_token: String, +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "secretsmanager")] + fn example_secretsmanager_secret_rotation_event() { + let data = include_bytes!("../../fixtures/example-secretsmanager-secret-rotation-event.json"); + let parsed: SecretsManagerSecretRotationEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SecretsManagerSecretRotationEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json b/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json new file mode 100644 index 00000000..f137ddbc --- /dev/null +++ b/lambda-events/src/fixtures/example-secretsmanager-secret-rotation-event.json @@ -0,0 +1,5 @@ +{ + "Step": "createSecret", + "SecretId": "arn:aws:secretsmanager:us-east-1:111122223333:secret:id-ABCD1E", + "ClientRequestToken": "1ab23456-cde7-8912-34fg-h56i78j9k12l" +} diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 4fd294e6..e21cdc13 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -153,6 +153,10 @@ pub use event::s3; #[cfg(feature = "s3")] pub use event::s3::batch_job as s3_batch_job; +/// AWS Lambda event definitions for secretsmanager. +#[cfg(feature = "secretsmanager")] +pub use event::secretsmanager; + /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] pub use event::ses; From 7bf39b13a2cdddb207947b2a58b12a0e5fff86ba Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Sun, 11 Feb 2024 00:22:22 +0900 Subject: [PATCH 085/211] Fix typo in types.rs (#815) --- lambda-runtime/src/types.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 85f6af78..3d89e0a0 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -109,7 +109,7 @@ impl Default for Context { } impl Context { - /// Create a new [Context] struct based on the fuction configuration + /// Create a new [Context] struct based on the function configuration /// and the incoming request data. pub fn new(request_id: &str, env_config: RefConfig, headers: &HeaderMap) -> Result { let client_context: Option = if let Some(value) = headers.get("lambda-runtime-client-context") { From 3567f2808b06dd6bc6e426fa6b78ee79cf6d8492 Mon Sep 17 00:00:00 2001 From: KIDANI Akito Date: Mon, 12 Feb 2024 03:26:13 +0900 Subject: [PATCH 086/211] Add websocket event fields for route (#816) --- lambda-events/src/event/apigw/mod.rs | 14 ++++++++ ...gw-websocket-request-disconnect-route.json | 33 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index f2cb097a..22c508b2 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -433,6 +433,10 @@ where pub route_key: Option, #[serde(default)] pub status: Option, + #[serde(default)] + pub disconnect_status_code: Option, + #[serde(default)] + pub disconnect_reason: Option, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. @@ -936,6 +940,16 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request_disconnect_route() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request-disconnect-route.json"); + let parsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayWebsocketProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_v2_custom_authorizer_v1_request() { diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json b/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json new file mode 100644 index 00000000..3a6f0cee --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request-disconnect-route.json @@ -0,0 +1,33 @@ +{ + "headers": { + "Host": "abcd1234.execute-api.us-east-1.amazonaws.com", + "x-api-key": "", + "X-Forwarded-For": "", + "x-restapi": "" + }, + "multiValueHeaders": { + "Host": [ "abcd1234.execute-api.us-east-1.amazonaws.com" ], + "x-api-key": [ "" ], + "X-Forwarded-For": [ "" ], + "x-restapi": [ "" ] + }, + "requestContext": { + "routeKey": "$disconnect", + "disconnectStatusCode": 1005, + "eventType": "DISCONNECT", + "extendedRequestId": "ABCD1234=", + "requestTime": "09/Feb/2024:18:23:28 +0000", + "messageDirection": "IN", + "disconnectReason": "Client-side close frame status not set", + "stage": "prod", + "connectedAt": 1707503007396, + "requestTimeEpoch": 1707503008941, + "identity": { "sourceIp": "192.0.2.1" }, + "requestId": "ABCD1234=", + "domainName": "abcd1234.execute-api.us-east-1.amazonaws.com", + "connectionId": "AAAA1234=", + "apiId": "abcd1234" + }, + "isBase64Encoded": false +} + \ No newline at end of file From 09819be9da158ef336b4f9aff372f3ebb25c8372 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 14 Feb 2024 00:16:36 +0800 Subject: [PATCH 087/211] Release events and http (#819) * Release lambda-events 0.13.2 * Release lambda-event 0.14.0 and lambda-http 0.9.3 --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index c6556b64..3010aedc 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.13.1" +version = "0.14.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1b9cd693..3843b7e0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.2" +version = "0.9.3" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.13.1" +version = "0.14.0" default-features = false features = ["alb", "apigw"] From 73736fdae3a7a3aa40e3d7d8ef75c0d1b954fe03 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Wed, 14 Feb 2024 01:20:22 +0000 Subject: [PATCH 088/211] Update lib.rs: Response vs Request in trace! log (#809) * Update lib.rs: Response vs Request in trace! log * Update lambda-runtime/src/lib.rs Co-authored-by: David Calavera --------- Co-authored-by: David Calavera --- lambda-runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 3a91d943..871991a7 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -126,7 +126,7 @@ impl Runtime { // Group the handling in one future and instrument it with the span async { let body = body.collect().await?.to_bytes(); - trace!("response body - {}", std::str::from_utf8(&body)?); + trace!(body = std::str::from_utf8(&body)?, "raw JSON event received from Lambda"); #[cfg(debug_assertions)] if parts.status.is_server_error() { From a4aa23ecd1d98fdba8f5a671f0959ad743bfe138 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 13 Feb 2024 17:33:07 -0800 Subject: [PATCH 089/211] Release runtime version 0.9.2 (#820) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 3843b7e0..9facc13d 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -33,7 +33,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.9", path = "../lambda-runtime" } +lambda_runtime = { version = "0.9.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 221aa6f0..94a3c201 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.1" +version = "0.9.2" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 871991a7..effb0561 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -126,7 +126,10 @@ impl Runtime { // Group the handling in one future and instrument it with the span async { let body = body.collect().await?.to_bytes(); - trace!(body = std::str::from_utf8(&body)?, "raw JSON event received from Lambda"); + trace!( + body = std::str::from_utf8(&body)?, + "raw JSON event received from Lambda" + ); #[cfg(debug_assertions)] if parts.status.is_server_error() { From b87214dc2ce3cdd8985b34cc1c7c5cad9dca1b05 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Tue, 20 Feb 2024 21:10:57 -0800 Subject: [PATCH 090/211] Allow null answerCorrect when deserializing (#826) CognitoEventUserPoolsVerifyAuthChallengeResponse. Co-authored-by: Michael Wallace --- lambda-events/src/event/cognito/mod.rs | 17 +++++++++- ...fy-auth-challenge-null-answer-correct.json | 31 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 95782ffc..6eb1c001 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -535,7 +535,7 @@ where #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { - #[serde(default)] + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub answer_correct: bool, } @@ -826,6 +826,21 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge_null_answer_correct() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json" + ); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(!parsed.response.answer_correct); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "cognito")] fn example_cognito_event_userpools_verify_auth_challenge_user_not_found() { diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json new file mode 100644 index 00000000..964061f2 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-null-answer-correct.json @@ -0,0 +1,31 @@ +{ + "version": "1", + "region": "us-west-2", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "aws-sdk-unknown-unknown", + "clientId": "" + }, + "triggerSource": "VerifyAuthChallengeResponse_Authentication", + "request": { + "userAttributes": { + "sub": "", + "cognito:user_status": "CONFIRMED", + "phone_number_verified": "true", + "cognito:phone_number_alias": "+12223334455", + "phone_number": "+12223334455" + }, + "privateChallengeParameters": { + "secret": "11122233" + }, + "challengeAnswer": "123xxxx", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + }, + "userNotFound": false + }, + "response": { + "answerCorrect": null + } +} From fabffbcb9eec856ab060de67522c356470d190fd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 21 Feb 2024 08:55:54 -0800 Subject: [PATCH 091/211] Api Gateway authorizer improvements (#827) * Improve Api Gateway events struct. - Remove the generic attribute since the authorizer values are not always of the same type, just use Value. - Match authorizer type to use the same type for all events. - Make the authorizer fields match the Java and DotNet implementation, which both use a Map for Rest and WebSocket events. -- https://github.com/aws/aws-lambda-dotnet/blob/master/Libraries/src/Amazon.Lambda.APIGatewayEvents/APIGatewayProxyRequest.cs -- https://github.com/aws/aws-lambda-java-libs/blob/main/aws-lambda-java-events/src/main/java/com/amazonaws/services/lambda/runtime/events/APIGatewayV2WebSocketEvent.java#L225 Signed-off-by: David Calavera * Expose RequestContext::authorizer() for Api Gateway requests. Signed-off-by :David Calavera * Add example on how to use Api Gateway authorizers in Axum. This also shows how to work with the RequestExt trait and the RequestContext object. Signed-off-by: David Calavera * Rename the Authorizer structs. Make the name more consistent since it's used for multiple versions of Api Gateway. Keep the old names as deprecated. Signed-off-by: David Calavera * Add example without using Axum extractor. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- .../http-axum-apigw-authorizer/Cargo.toml | 14 ++ examples/http-axum-apigw-authorizer/README.md | 13 ++ .../http-axum-apigw-authorizer/src/main.rs | 93 +++++++++++ lambda-events/src/event/apigw/mod.rs | 158 +++++++++++------- lambda-http/src/request.rs | 34 ++++ 5 files changed, 249 insertions(+), 63 deletions(-) create mode 100644 examples/http-axum-apigw-authorizer/Cargo.toml create mode 100644 examples/http-axum-apigw-authorizer/README.md create mode 100644 examples/http-axum-apigw-authorizer/src/main.rs diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml new file mode 100644 index 00000000..5c3e806e --- /dev/null +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-axum-apigw-authorizer" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.7" +lambda_http = { path = "../../lambda-http" } +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1.0.196" +serde_json = "1.0" +tokio = { version = "1", features = ["macros"] } +tracing = { version = "0.1", features = ["log"] } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-apigw-authorizer/README.md b/examples/http-axum-apigw-authorizer/README.md new file mode 100644 index 00000000..2d05df59 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/README.md @@ -0,0 +1,13 @@ +# Axum example that integrates with Api Gateway authorizers + +This example shows how to extract information from the Api Gateway Request Authorizer in an Axum handler. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs new file mode 100644 index 00000000..bb03de07 --- /dev/null +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -0,0 +1,93 @@ +use axum::{ + async_trait, + extract::{FromRequest, Request}, + http::StatusCode, + response::Json, + routing::get, + Router, +}; +use lambda_http::{run, Error, RequestExt}; +use serde_json::{json, Value}; +use std::{collections::HashMap, env::set_var}; + +struct AuthorizerField(String); +struct AuthorizerFields(HashMap); + +#[async_trait] +impl FromRequest for AuthorizerField +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .and_then(|a| a.fields.get("field_name")) + .and_then(|f| f.as_str()) + .map(|v| Self(v.to_string())) + .ok_or((StatusCode::BAD_REQUEST, "`field_name` authorizer field is missing")) + } +} + +#[async_trait] +impl FromRequest for AuthorizerFields +where + S: Send + Sync, +{ + type Rejection = (StatusCode, &'static str); + + async fn from_request(req: Request, _state: &S) -> Result { + req.request_context_ref() + .and_then(|r| r.authorizer()) + .map(|a| Self(a.fields.clone())) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing")) + } +} + +async fn extract_field(AuthorizerField(field): AuthorizerField) -> Json { + Json(json!({ "field extracted": field })) +} + +async fn extract_all_fields(AuthorizerFields(fields): AuthorizerFields) -> Json { + Json(json!({ "authorizer fields": fields })) +} + +async fn authorizer_without_extractor(req: Request) -> Result, (StatusCode, &'static str)> { + let auth = req + .request_context_ref() + .and_then(|r| r.authorizer()) + .ok_or((StatusCode::BAD_REQUEST, "authorizer is missing"))?; + + let field1 = auth.fields.get("field1").and_then(|v| v.as_str()).unwrap_or_default(); + let field2 = auth.fields.get("field2").and_then(|v| v.as_str()).unwrap_or_default(); + + Ok(Json(json!({ "field1": field1, "field2": field2 }))) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // If you use API Gateway stages, the Rust Runtime will include the stage name + // as part of the path that your application receives. + // Setting the following environment variable, you can remove the stage from the path. + // This variable only applies to API Gateway stages, + // you can remove it if you don't use them. + // 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) + // disable printing the name of the module in every log line. + .with_target(false) + // disabling time is handy because CloudWatch will add the ingestion time. + .without_time() + .init(); + + let app = Router::new() + .route("/extract-field", get(extract_field)) + .route("/extract-all-fields", get(extract_all_fields)) + .route("/authorizer-without-extractor", get(authorizer_without_extractor)); + + run(app).await +} diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 22c508b2..1a9b1f1a 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -5,19 +5,14 @@ use crate::custom_serde::{ use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -44,7 +39,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayProxyRequestContext, + pub request_context: ApiGatewayProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -72,11 +67,7 @@ pub struct ApiGatewayProxyResponse { /// Lambda function. It also includes Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -99,10 +90,13 @@ where pub resource_path: Option, #[serde(default)] pub path: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - pub authorizer: HashMap, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(with = "http_method")] pub http_method: Method, #[serde(default)] @@ -168,11 +162,7 @@ pub struct ApiGatewayV2httpRequest { /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContext -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayV2httpRequestContext { #[serde(default)] pub route_key: Option, #[serde(default)] @@ -181,9 +171,9 @@ where pub stage: Option, #[serde(default)] pub request_id: Option, - #[serde(bound = "", default)] + #[serde(default)] #[serde(skip_serializing_if = "Option::is_none")] - pub authorizer: Option>, + pub authorizer: Option, /// The API Gateway HTTP API Id #[serde(default)] #[serde(rename = "apiId")] @@ -201,29 +191,27 @@ where pub authentication: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerDescription` contains authorizer information for the request context. +/// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerDescription -where - T1: DeserializeOwned, - T1: Serialize, -{ +pub struct ApiGatewayRequestAuthorizer { #[serde(skip_serializing_if = "Option::is_none")] - pub jwt: Option, - #[serde(deserialize_with = "deserialize_lambda_map")] - #[serde(default)] - #[serde(bound = "")] - #[serde(skip_serializing_if = "HashMap::is_empty")] - pub lambda: HashMap, + pub jwt: Option, + #[serde( + bound = "", + rename = "lambda", + default, + skip_serializing_if = "HashMap::is_empty", + deserialize_with = "deserialize_lambda_map" + )] + pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] - pub iam: Option, + pub iam: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerJwtDescription` contains JWT authorizer information for the request context. +/// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { +pub struct ApiGatewayRequestAuthorizerJwtDescription { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub claims: HashMap, @@ -231,10 +219,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { pub scopes: Option>, } -/// `ApiGatewayV2httpRequestContextAuthorizerIamDescription` contains IAM information for the request context. +/// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { +pub struct ApiGatewayRequestAuthorizerIamDescription { #[serde(default)] pub access_key: Option, #[serde(default)] @@ -242,7 +230,7 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { #[serde(default)] pub caller_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub cognito_identity: Option, + pub cognito_identity: Option, #[serde(default)] pub principal_org_id: Option, #[serde(default)] @@ -251,10 +239,10 @@ pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { pub user_id: Option, } -/// `ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +/// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity { +pub struct ApiGatewayRequestAuthorizerCognitoIdentity { pub amr: Vec, #[serde(default)] pub identity_id: Option, @@ -332,13 +320,7 @@ pub struct ApiGatewayRequestIdentity { /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequest -where - T1: DeserializeOwned + Default, - T1: Serialize, - T2: DeserializeOwned + Default, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequest { /// The resource path defined in API Gateway #[serde(default)] pub resource: Option, @@ -367,7 +349,7 @@ where #[serde(default)] pub stage_variables: HashMap, #[serde(bound = "")] - pub request_context: ApiGatewayWebsocketProxyRequestContext, + pub request_context: ApiGatewayWebsocketProxyRequestContext, #[serde(default)] pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] @@ -379,13 +361,7 @@ where /// Cognito identity information for the caller. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct ApiGatewayWebsocketProxyRequestContext -where - T1: DeserializeOwned, - T1: Serialize, - T2: DeserializeOwned, - T2: Serialize, -{ +pub struct ApiGatewayWebsocketProxyRequestContext { #[serde(default)] pub account_id: Option, #[serde(default)] @@ -398,8 +374,13 @@ where pub identity: ApiGatewayRequestIdentity, #[serde(default)] pub resource_path: Option, - #[serde(bound = "")] - pub authorizer: Option, + #[serde( + default, + deserialize_with = "deserialize_authorizer_fields", + serialize_with = "serialize_authorizer_fields", + skip_serializing_if = "ApiGatewayRequestAuthorizer::is_empty" + )] + pub authorizer: ApiGatewayRequestAuthorizer, #[serde(deserialize_with = "http_method::deserialize_optional")] #[serde(serialize_with = "http_method::serialize_optional")] #[serde(skip_serializing_if = "Option::is_none")] @@ -425,7 +406,7 @@ where #[serde(default)] pub message_direction: Option, #[serde(bound = "")] - pub message_id: Option, + pub message_id: Option, #[serde(default)] pub request_time: Option, pub request_time_epoch: i64, @@ -768,6 +749,45 @@ fn default_http_method() -> Method { Method::GET } +#[deprecated = "use `ApiGatewayRequestAuthorizer` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerDescription = ApiGatewayRequestAuthorizer; +#[deprecated = "use `ApiGatewayRequestAuthorizerJwtDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerJwtDescription = ApiGatewayRequestAuthorizerJwtDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerIamDescription` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerIamDescription = ApiGatewayRequestAuthorizerIamDescription; +#[deprecated = "use `ApiGatewayRequestAuthorizerCognitoIdentity` instead"] +pub type ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity = ApiGatewayRequestAuthorizerCognitoIdentity; + +impl ApiGatewayRequestAuthorizer { + fn is_empty(&self) -> bool { + self.fields.is_empty() + } +} + +fn deserialize_authorizer_fields<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let fields: Option> = Option::deserialize(deserializer)?; + let mut authorizer = ApiGatewayRequestAuthorizer::default(); + if let Some(fields) = fields { + authorizer.fields = fields; + } + + Ok(authorizer) +} + +pub fn serialize_authorizer_fields( + authorizer: &ApiGatewayRequestAuthorizer, + ser: S, +) -> Result { + let mut map = ser.serialize_map(Some(authorizer.fields.len()))?; + for (k, v) in &authorizer.fields { + map.serialize_entry(k, v)?; + } + map.end() +} + #[cfg(test)] mod test { use super::*; @@ -991,4 +1011,16 @@ mod test { let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_request_authorizer_fields() { + let data = include_bytes!("../../fixtures/example-apigw-request.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + + let fields = parsed.request_context.authorizer.fields; + assert_eq!(Some("admin"), fields.get("principalId").unwrap().as_str()); + assert_eq!(Some(1), fields.get("clientId").unwrap().as_u64()); + assert_eq!(Some("Exata"), fields.get("clientName").unwrap().as_str()); + } } diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index bce2e3d3..9e789270 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -17,6 +17,8 @@ use crate::ext::extensions::{PathParameters, StageVariables}; use crate::ext::extensions::{QueryStringParameters, RawHttpPath}; #[cfg(feature = "alb")] use aws_lambda_events::alb::{AlbTargetGroupRequest, AlbTargetGroupRequestContext}; +#[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] +use aws_lambda_events::apigw::ApiGatewayRequestAuthorizer; #[cfg(feature = "apigw_rest")] use aws_lambda_events::apigw::{ApiGatewayProxyRequest, ApiGatewayProxyRequestContext}; #[cfg(feature = "apigw_http")] @@ -425,6 +427,22 @@ impl From for http::Request { } } +impl RequestContext { + /// Returns the Api Gateway Authorizer information for a request. + #[cfg(any(feature = "apigw_rest", feature = "apigw_http", feature = "apigw_websockets"))] + pub fn authorizer(&self) -> Option<&ApiGatewayRequestAuthorizer> { + match self { + #[cfg(feature = "apigw_rest")] + Self::ApiGatewayV1(ag) => Some(&ag.authorizer), + #[cfg(feature = "apigw_http")] + Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(), + #[cfg(feature = "apigw_websockets")] + Self::WebSocket(ag) => Some(&ag.authorizer), + _ => None, + } + } +} + /// Deserializes a `Request` from a `Read` impl providing JSON events. /// /// # Example @@ -920,4 +938,20 @@ mod tests { assert_eq!(vec!["val1", "val2"], query.0.my_key); assert_eq!(vec!["val3", "val4"], query.0.my_other_key); } + + #[test] + #[cfg(feature = "apigw_rest")] + fn deserializes_request_authorizer() { + let input = include_str!("../../lambda-events/src/fixtures/example-apigw-request.json"); + let result = from_str(input); + assert!( + result.is_ok(), + "event was not parsed as expected {result:?} given {input}" + ); + let req = result.expect("failed to parse request"); + + let req_context = req.request_context_ref().expect("Request is missing RequestContext"); + let authorizer = req_context.authorizer().expect("authorizer is missing"); + assert_eq!(Some("admin"), authorizer.fields.get("principalId").unwrap().as_str()); + } } From 387b07c7250616e9fefcafada410eb2a44d1c4b3 Mon Sep 17 00:00:00 2001 From: Kikuo Emoto Date: Thu, 22 Feb 2024 08:50:08 +0900 Subject: [PATCH 092/211] Allow error response customization (#828) * Feat: custom error type support Error responses of `lambda_runtime` are now customizable. One use case is to avoid using `std::any::type_name` for error types, which is not reliable for determining subsequent program behavior. `F::Error` of `Runtime::run` and `run` is required to implement `Into>` instead of `Display`. `EventErrorRequest` accepts a value implementing `Into>` instead of the error type and message. `Diagnostic` becomes public because users may implement their own conversions. Its fields are wrapped in `Cow` so that they can carry both borrowed and owned strings. `Diagnostic` is implemented for every type that implements `Display` as a fallback, which also ensures backward compatibility. * Chore: add example to comments on Diagnostic The comments on `Diagnositc` shows how to customize error responses with an example. --- lambda-runtime/src/lib.rs | 35 ++++++++++------- lambda-runtime/src/requests.rs | 11 ++---- lambda-runtime/src/types.rs | 68 +++++++++++++++++++++++++++++++--- 3 files changed, 87 insertions(+), 27 deletions(-) diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index effb0561..b4964954 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -14,8 +14,9 @@ use hyper::{body::Incoming, http::Request}; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, env, - fmt::{self, Debug, Display}, + fmt::{self, Debug}, future::Future, panic, sync::Arc, @@ -33,7 +34,9 @@ pub mod streaming; mod types; use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; -pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; +pub use types::{ + Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, +}; use types::invoke_request_id; @@ -96,7 +99,7 @@ impl Runtime { where F: Service>, F::Future: Future>, - F::Error: fmt::Debug + fmt::Display, + F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, @@ -173,7 +176,14 @@ impl Runtime { } else { "Lambda panicked".to_string() }; - EventErrorRequest::new(request_id, error_type, &msg).into_req() + EventErrorRequest::new( + request_id, + Diagnostic { + error_type: Cow::Borrowed(error_type), + error_message: Cow::Owned(msg), + }, + ) + .into_req() } } } @@ -224,7 +234,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service>, F::Future: Future>, - F::Error: fmt::Debug + fmt::Display, + F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, @@ -249,15 +259,12 @@ fn type_name_of_val(_: T) -> &'static str { std::any::type_name::() } -fn build_event_error_request(request_id: &str, err: T) -> Result, Error> +fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, Error> where - T: Display + Debug, + T: Into> + Debug, { error!("{:?}", err); // logs the error in CloudWatch - let error_type = type_name_of_val(&err); - let msg = format!("{err}"); - - EventErrorRequest::new(request_id, error_type, &msg).into_req() + EventErrorRequest::new(request_id, err).into_req() } #[cfg(test)] @@ -274,7 +281,7 @@ mod endpoint_tests { use httpmock::prelude::*; use lambda_runtime_api_client::Client; - use std::{env, sync::Arc}; + use std::{borrow::Cow, env, sync::Arc}; use tokio_stream::StreamExt; #[tokio::test] @@ -341,8 +348,8 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { let diagnostic = Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data"), }; let body = serde_json::to_string(&diagnostic)?; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index c9274cf4..d1e25e32 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -194,13 +194,10 @@ pub(crate) struct EventErrorRequest<'a> { } impl<'a> EventErrorRequest<'a> { - pub(crate) fn new(request_id: &'a str, error_type: &'a str, error_message: &'a str) -> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, diagnostic: impl Into>) -> EventErrorRequest<'a> { EventErrorRequest { request_id, - diagnostic: Diagnostic { - error_type, - error_message, - }, + diagnostic: diagnostic.into(), } } } @@ -226,8 +223,8 @@ fn test_event_error_request() { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data", + error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), + error_message: std::borrow::Cow::Borrowed("Error parsing event data"), }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 3d89e0a0..478f88fd 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -5,19 +5,75 @@ use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, collections::HashMap, env, - fmt::Debug, + fmt::{Debug, Display}, time::{Duration, SystemTime}, }; use tokio_stream::Stream; use tracing::Span; +/// Diagnostic information about an error. +/// +/// `Diagnostic` is automatically derived for types that implement +/// [`Display`][std::fmt::Display]; e.g., [`Error`][std::error::Error]. +/// +/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of +/// the original error with [`std::any::type_name`] as a fallback, which may +/// not be reliable for conditional error handling. +/// You can define your own error container that implements `Into` +/// if you need to handle errors based on error types. +/// +/// Example: +/// ``` +/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; +/// use std::borrow::Cow; +/// +/// #[derive(Debug)] +/// struct ErrorResponse(Error); +/// +/// impl<'a> Into> for ErrorResponse { +/// fn into(self) -> Diagnostic<'a> { +/// Diagnostic { +/// error_type: Cow::Borrowed("MyError"), +/// error_message: Cow::Owned(self.0.to_string()), +/// } +/// } +/// } +/// +/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { +/// // ... do something +/// Ok(()) +/// } +/// ``` #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub(crate) struct Diagnostic<'a> { - pub(crate) error_type: &'a str, - pub(crate) error_message: &'a str, +pub struct Diagnostic<'a> { + /// Error type. + /// + /// `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`] as a fallback. + /// Please implement your own `Into` if you need more reliable + /// error types. + pub error_type: Cow<'a, str>, + /// Error message. + /// + /// `error_message` is the output from the [`Display`][std::fmt::Display] + /// implementation of the original error as a fallback. + pub error_message: Cow<'a, str>, +} + +impl<'a, T> From for Diagnostic<'a> +where + T: Display, +{ + fn from(value: T) -> Self { + Diagnostic { + error_type: Cow::Borrowed(std::any::type_name::()), + error_message: Cow::Owned(format!("{value}")), + } + } } /// Client context sent by the AWS Mobile SDK. @@ -315,8 +371,8 @@ mod test { }); let actual = Diagnostic { - error_type: "InvalidEventDataError", - error_message: "Error parsing event data.", + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data."), }; let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); assert_eq!(expected, actual); From 0d92dd347d6841182a954399fe80421b7cf214c0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 23 Feb 2024 11:32:42 -0800 Subject: [PATCH 093/211] Implement CloudWatch alarm SNS payloads. (#829) Signed-off-by: David Calavera --- lambda-events/src/custom_serde/mod.rs | 1 + lambda-events/src/event/sns/mod.rs | 80 ++++++++++++++++++++++++++- 2 files changed, 80 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 82723c3f..46d121d1 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -80,6 +80,7 @@ where feature = "cloudwatch_events", feature = "code_commit", feature = "cognito", + feature = "sns", test ))] pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index e9809630..d72b926a 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -3,7 +3,7 @@ use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; -use crate::custom_serde::deserialize_lambda_map; +use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// @@ -175,6 +175,78 @@ pub struct MessageAttribute { pub value: String, } +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchAlarmPayload { + pub alarm_name: String, + pub alarm_description: String, + #[serde(rename = "AWSAccountId")] + pub aws_account_id: String, + pub new_state_value: String, + pub new_state_reason: String, + pub state_change_time: String, + pub region: String, + pub alarm_arn: String, + pub old_state_value: String, + pub trigger: CloudWatchAlarmTrigger, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchAlarmTrigger { + pub period: i64, + pub evaluation_periods: i64, + pub comparison_operator: String, + pub threshold: f64, + pub treat_missing_data: String, + pub evaluate_low_sample_count_percentile: String, + #[serde(default)] + pub metrics: Vec, + pub metric_name: Option, + pub namespace: Option, + pub statistic_type: Option, + pub statistic: Option, + pub unit: Option, + #[serde(default)] + pub dimensions: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetricDataQuery { + pub id: String, + pub expression: Option, + pub label: Option, + pub metric_stat: Option, + pub period: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub return_data: bool, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetricStat { + pub metric: CloudWatchMetric, + pub period: i64, + pub stat: String, + pub unit: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudWatchMetric { + #[serde(default)] + pub dimensions: Vec, + pub metric_name: Option, + pub namespace: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CloudWatchDimension { + pub name: String, + pub value: String, +} + #[cfg(test)] mod test { use super::*; @@ -209,6 +281,12 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + + let parsed: SnsEventObj = + serde_json::from_slice(data).expect("failed to parse CloudWatch Alarm payload"); + + let record = parsed.records.first().unwrap(); + assert_eq!("EXAMPLE", record.sns.message.alarm_name); } #[test] From af4df1f7db21dbe694b4a6ce720626854939a242 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 18:39:01 -0800 Subject: [PATCH 094/211] Remove unused warning. (#831) Signed-off-by: David Calavera --- lambda-http/src/request.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9e789270..9476da3b 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -438,6 +438,7 @@ impl RequestContext { Self::ApiGatewayV2(ag) => ag.authorizer.as_ref(), #[cfg(feature = "apigw_websockets")] Self::WebSocket(ag) => Some(&ag.authorizer), + #[cfg(any(feature = "alb", feature = "pass_through"))] _ => None, } } From f51589c247171984e905e0c42b3bf3faad4ea11d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 19:43:30 -0800 Subject: [PATCH 095/211] Advanced logging controls (#830) * Implement Lambda's advance logging controls. Provide a feature that exposes a initialization function for `tracing-subscriber` that sets the right logging controls based on Lambda's configuration. The feature is enabled by default, but it can be disabled if a user doesn't want to use it. Signed-off-by: David Calavera * Update examples to use the new tracing-subscriber feature. Signed-off-by: David Calavera * Remove tracing from the runtime client. It makes logs too verbose. Signed-off-by: David Calavera * Make the tracing dependency optional. Signed-off-by: David Calavera * Fix ambiguous name in old versions of Rust. Identify the dependency as the top dependency. Signed-off-by: David Calavera * Fix formatting. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- README.md | 18 ++++++++ .../consumer/Cargo.toml | 7 +--- .../consumer/src/main.rs | 11 ++--- .../producer/Cargo.toml | 6 +-- .../producer/src/main.rs | 13 ++---- .../Cargo.toml | 2 - .../src/main.rs | 15 +++---- examples/basic-error-handling/Cargo.toml | 11 ----- examples/basic-error-handling/src/main.rs | 10 +---- .../basic-lambda-external-runtime/Cargo.toml | 7 ++-- .../basic-lambda-external-runtime/src/main.rs | 10 +---- examples/basic-lambda/Cargo.toml | 11 +---- examples/basic-lambda/src/main.rs | 10 +---- .../Cargo.toml | 2 - .../src/main.rs | 10 +---- .../src/s3.rs | 1 + examples/basic-s3-thumbnail/Cargo.toml | 2 - examples/basic-s3-thumbnail/src/main.rs | 10 +---- examples/basic-s3-thumbnail/src/s3.rs | 1 + examples/basic-sdk/Cargo.toml | 2 - examples/basic-sdk/src/main.rs | 10 +---- examples/basic-shared-resource/Cargo.toml | 10 ----- examples/basic-shared-resource/src/main.rs | 10 +---- examples/basic-sqs/Cargo.toml | 2 - examples/basic-sqs/src/main.rs | 10 +---- examples/basic-streaming-response/Cargo.toml | 2 - examples/basic-streaming-response/src/main.rs | 10 +---- examples/extension-basic/Cargo.toml | 11 ----- examples/extension-basic/src/main.rs | 10 +---- examples/extension-combined/Cargo.toml | 11 ----- examples/extension-combined/src/main.rs | 15 ++----- examples/extension-custom-events/Cargo.toml | 11 ----- examples/extension-custom-events/src/main.rs | 10 +---- examples/extension-custom-service/Cargo.toml | 11 ----- examples/extension-custom-service/src/main.rs | 10 +---- examples/extension-internal-flush/src/main.rs | 3 +- examples/extension-logs-basic/Cargo.toml | 11 ----- examples/extension-logs-basic/src/main.rs | 10 +---- .../extension-logs-custom-service/Cargo.toml | 11 ----- .../extension-logs-custom-service/src/main.rs | 15 ++----- .../Cargo.toml | 9 ---- .../src/main.rs | 10 +---- examples/extension-telemetry-basic/Cargo.toml | 11 ----- .../extension-telemetry-basic/src/main.rs | 21 ++++------ .../http-axum-apigw-authorizer/Cargo.toml | 2 - .../http-axum-apigw-authorizer/src/main.rs | 10 +---- examples/http-axum-diesel-ssl/Cargo.toml | 9 ---- examples/http-axum-diesel-ssl/src/main.rs | 18 ++------ examples/http-axum-diesel/Cargo.toml | 9 ---- examples/http-axum-diesel/src/main.rs | 10 +---- examples/http-axum-middleware/Cargo.toml | 7 +--- examples/http-axum-middleware/src/main.rs | 8 +--- examples/http-axum/Cargo.toml | 9 ---- examples/http-axum/src/main.rs | 10 +---- examples/http-basic-lambda/Cargo.toml | 11 ----- examples/http-basic-lambda/src/main.rs | 10 +---- examples/http-cors/Cargo.toml | 11 ----- examples/http-cors/src/main.rs | 10 +---- examples/http-dynamodb/Cargo.toml | 12 ------ examples/http-dynamodb/src/main.rs | 17 +++----- examples/http-query-parameters/Cargo.toml | 11 ----- examples/http-query-parameters/src/main.rs | 10 +---- examples/http-raw-path/Cargo.toml | 11 ----- examples/http-raw-path/src/main.rs | 10 +---- examples/http-shared-resource/Cargo.toml | 11 ----- examples/http-shared-resource/src/main.rs | 10 +---- examples/http-tower-trace/Cargo.toml | 9 ---- examples/http-tower-trace/src/main.rs | 10 +---- lambda-extension/Cargo.toml | 6 ++- lambda-extension/src/lib.rs | 4 ++ lambda-http/Cargo.toml | 3 +- lambda-http/src/lib.rs | 6 ++- lambda-runtime-api-client/Cargo.toml | 6 +++ lambda-runtime-api-client/src/lib.rs | 3 ++ lambda-runtime-api-client/src/tracing.rs | 41 +++++++++++++++++++ lambda-runtime/Cargo.toml | 4 +- lambda-runtime/src/lib.rs | 7 +++- 77 files changed, 187 insertions(+), 541 deletions(-) create mode 100644 lambda-runtime-api-client/src/tracing.rs diff --git a/README.md b/README.md index 43c38586..8a5920b7 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,24 @@ You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/c Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. +## Tracing and Logging + +The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. + +By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subsriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: + +```rust +use lambda_runtime::{run, service_fn, tracing, Error}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + run(service_fn(|event| tracing::info!(?event))).await +} +``` + +The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) if they're configured for your function. By default, the log level to emit events is `INFO`. + ## AWS event objects This project includes Lambda event struct definitions, [`aws_lambda_events`](https://crates.io/crates/aws_lambda_events). This crate can be leveraged to provide strongly-typed Lambda event structs. You can create your own custom event objects and their corresponding structs as well. diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml index 8555a073..69ec04a0 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -3,19 +3,14 @@ name = "consumer" version = "0.1.0" edition = "2021" - [dependencies] -#tracing -tracing = "0.1.40" -tracing-subscriber = "0.3.17" - #aws dependencies aws-sdk-config = "0.35.0" aws-sdk-sqs = "0.35.0" aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } #lambda runtime -lambda_runtime = "0.8.1" +lambda_runtime = { path = "../../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } #shared lib diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs index 42290192..c076a502 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/src/main.rs @@ -1,15 +1,10 @@ use aws_lambda_events::event::sqs::SqsEventObj; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use pizza_lib::Pizza; #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .with_ansi(false) - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(func); lambda_runtime::run(func).await?; Ok(()) @@ -18,7 +13,7 @@ async fn main() -> Result<(), Error> { async fn func(event: LambdaEvent>) -> Result<(), Error> { for record in event.payload.records.iter() { let pizza = &record.body; - println!("Pizza name: {} with toppings: {:?}", pizza.name, pizza.toppings); + tracing::info!(name = pizza.name, toppings = ?pizza.toppings, "pizza order received"); } Ok(()) } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml index 557ac6e5..83aa48ab 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -7,17 +7,13 @@ edition = "2021" env = { "QUEUE_URL" = "https://changeMe" } [dependencies] -#tracing -tracing = "0.1.40" -tracing-subscriber = "0.3.17" - #aws dependencies aws-config = "0.57.1" aws-sdk-config = "0.35.0" aws-sdk-sqs = "0.35.0" #lambda runtime -lambda_runtime = "0.8.1" +lambda_runtime = { path = "../../../lambda-runtime" } serde_json = "1.0.108" tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs index 2cc2541b..2a70dce3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -1,4 +1,4 @@ -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use pizza_lib::Pizza; use serde_json::{json, Value}; @@ -15,12 +15,7 @@ impl SQSManager { #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .with_ansi(false) - .without_time() - .init(); + tracing::init_default_subscriber(); // read the queue url from the environment let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); @@ -31,9 +26,7 @@ async fn main() -> Result<(), Error> { let sqs_manager_ref = &sqs_manager; // no need to create a SQS Client for each incoming request, let's use a shared state - let handler_func_closure = |event: LambdaEvent| async move { - process_event(event, sqs_manager_ref).await - }; + let handler_func_closure = |event: LambdaEvent| async move { process_event(event, sqs_manager_ref).await }; lambda_runtime::run(service_fn(handler_func_closure)).await?; Ok(()) } diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 04158320..95050b9a 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -12,5 +12,3 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } futures = "0.3" -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 23faa68f..42bb2253 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -3,9 +3,12 @@ use aws_lambda_events::{ sqs::{BatchItemFailure, SqsBatchResponse, SqsMessageObj}, }; use futures::Future; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{ + run, service_fn, + tracing::{self, Instrument}, + Error, LambdaEvent, +}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use tracing::Instrument; /// [To customize] Your object definition, sent to the SQS queue triggering this lambda. #[derive(Deserialize, Serialize)] @@ -29,13 +32,7 @@ async fn data_handler(data: Data) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run_sqs_partial_batch_failure(data_handler).await } diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index e8699141..1039a139 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -3,20 +3,9 @@ name = "error-handling" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" serde_json = "1.0.81" simple-error = "0.2.3" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 0939d2d0..528d6f02 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,5 +1,5 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::fs::File; @@ -50,13 +50,7 @@ impl std::fmt::Display for CustomError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // call the actual handler of the request let func = service_fn(func); diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index 0682efaf..d6d023d8 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -6,10 +6,9 @@ edition = "2021" [dependencies] async-channel = "1.8.0" futures-lite = "1.13.0" -lambda_runtime = "0.8.0" -lambda_runtime_api_client = "0.8.0" +lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.163" tokio = "1.28.2" + +[dev-dependencies] tokio-test = "0.4.2" -tracing = "0.1.37" -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index 9419b17b..bd3b4e6c 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -1,7 +1,7 @@ use std::{io, thread}; use futures_lite::future; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use tokio::runtime::Builder; @@ -25,13 +25,7 @@ struct Response { fn main() -> Result<(), io::Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Create a channel used to send and receive outputs from our lambda handler. Realistically, this would be either an unbounded channel // or a bounded channel with a higher capacity as needed. diff --git a/examples/basic-lambda/Cargo.toml b/examples/basic-lambda/Cargo.toml index cd2efa42..6fad6990 100644 --- a/examples/basic-lambda/Cargo.toml +++ b/examples/basic-lambda/Cargo.toml @@ -3,17 +3,10 @@ name = "basic-lambda" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } + +[dev-dependencies] tokio-test = "0.4.2" \ No newline at end of file diff --git a/examples/basic-lambda/src/main.rs b/examples/basic-lambda/src/main.rs index 09145bb3..8630e78c 100644 --- a/examples/basic-lambda/src/main.rs +++ b/examples/basic-lambda/src/main.rs @@ -1,7 +1,7 @@ // This example requires the following input to succeed: // { "command": "do something" } -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -25,13 +25,7 @@ struct Response { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_handler); lambda_runtime::run(func).await?; diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index 79640cc2..ba750df9 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -19,8 +19,6 @@ aws_lambda_events = "0.8.3" lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" thumbnailer = "0.4.0" diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 328e7500..bb1c4f9c 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -2,7 +2,7 @@ use std::error; use aws_lambda_events::s3::object_lambda::{GetObjectContext, S3ObjectLambdaEvent}; use aws_sdk_s3::Client as S3Client; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use s3::{GetFile, SendFile}; mod s3; @@ -57,13 +57,7 @@ fn get_thumbnail(vec: Vec, size: u32) -> Vec { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::TRACE) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs index 71e03ffc..daba3739 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use lambda_runtime::tracing; use std::{error, io::Read}; pub trait GetFile { diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 6bbe11b7..1cd3b834 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -19,8 +19,6 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1" } -tracing-subscriber = { version = "0.3", default-features = false, features = ["ansi", "fmt"] } aws-config = "0.55" aws-smithy-http = "0.55.3" aws-sdk-s3 = "0.28" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d92c822b..3eb5bfe9 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -1,6 +1,6 @@ use aws_lambda_events::{event::s3::S3Event, s3::S3EventRecord}; use aws_sdk_s3::Client as S3Client; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use s3::{GetFile, PutFile}; mod s3; @@ -109,13 +109,7 @@ fn get_thumbnail(vec: Vec, size: u32) -> Result, String> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 17d7f975..0dd8629d 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -2,6 +2,7 @@ use async_trait::async_trait; use aws_sdk_s3::operation::get_object::GetObjectError; use aws_sdk_s3::Client as S3Client; use aws_smithy_http::byte_stream::ByteStream; +use lambda_runtime::tracing; #[async_trait] pub trait GetFile { diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 0e930f7c..454a970f 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -12,8 +12,6 @@ aws-sdk-s3 = "0.24" lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index 6e2654a4..d49c84e1 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -1,6 +1,6 @@ use async_trait::async_trait; use aws_sdk_s3::{output::ListObjectsV2Output, Client as S3Client}; -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// The request defines what bucket to list @@ -34,13 +34,7 @@ impl ListObjects for S3Client { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let shared_config = aws_config::load_from_env().await; let client = S3Client::new(&shared_config); diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index b3e2faa5..2aad5886 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -3,17 +3,7 @@ name = "shared-resource" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - diff --git a/examples/basic-shared-resource/src/main.rs b/examples/basic-shared-resource/src/main.rs index 15ababa0..795c2c97 100644 --- a/examples/basic-shared-resource/src/main.rs +++ b/examples/basic-shared-resource/src/main.rs @@ -4,7 +4,7 @@ // Run it with the following input: // { "command": "do something" } -use lambda_runtime::{service_fn, Error, LambdaEvent}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// This is also a made-up example. Requests come into the runtime as unicode @@ -45,13 +45,7 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let client = SharedClient::new("Shared Client 1 (perhaps a database)"); let client_ref = &client; diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 9d259218..0df7d8e2 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -19,5 +19,3 @@ aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/basic-sqs/src/main.rs b/examples/basic-sqs/src/main.rs index 63967893..f04272be 100644 --- a/examples/basic-sqs/src/main.rs +++ b/examples/basic-sqs/src/main.rs @@ -1,5 +1,5 @@ use aws_lambda_events::event::sqs::SqsEventObj; -use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; /// Object that you send to SQS and plan to process on the function. @@ -21,13 +21,7 @@ async fn function_handler(event: LambdaEvent>) -> Result<(), E #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/basic-streaming-response/Cargo.toml b/examples/basic-streaming-response/Cargo.toml index e9b7499c..3f1bacac 100644 --- a/examples/basic-streaming-response/Cargo.toml +++ b/examples/basic-streaming-response/Cargo.toml @@ -8,6 +8,4 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } serde_json = "1.0" \ No newline at end of file diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index c8932554..8533c8e3 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -1,7 +1,7 @@ use lambda_runtime::{ service_fn, streaming::{channel, Body, Response}, - Error, LambdaEvent, + tracing, Error, LambdaEvent, }; use serde_json::Value; use std::{thread, time::Duration}; @@ -24,13 +24,7 @@ async fn func(_event: LambdaEvent) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); lambda_runtime::run(service_fn(func)).await?; Ok(()) diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index caf0818c..48e2ed51 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-basic/src/main.rs b/examples/extension-basic/src/main.rs index f9838c6b..e76d638f 100644 --- a/examples/extension-basic/src/main.rs +++ b/examples/extension-basic/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -15,13 +15,7 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_extension); lambda_extension::run(func).await diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index d776f488..2a745c7b 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-combined" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-combined/src/main.rs b/examples/extension-combined/src/main.rs index e05b1b7d..ce6054e8 100644 --- a/examples/extension-combined/src/main.rs +++ b/examples/extension-combined/src/main.rs @@ -1,7 +1,6 @@ use lambda_extension::{ - service_fn, Error, Extension, LambdaEvent, LambdaLog, LambdaLogRecord, NextEvent, SharedService, + service_fn, tracing, Error, Extension, LambdaEvent, LambdaLog, LambdaLogRecord, NextEvent, SharedService, }; -use tracing::info; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -18,8 +17,8 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { async fn my_log_processor(logs: Vec) -> Result<(), Error> { for log in logs { match log.record { - LambdaLogRecord::Function(record) => info!("[logs] [function] {}", record), - LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}", record), + LambdaLogRecord::Function(record) => tracing::info!("[logs] [function] {}", record), + LambdaLogRecord::Extension(record) => tracing::info!("[logs] [extension] {}", record), _ => (), } } @@ -30,13 +29,7 @@ async fn my_log_processor(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let func = service_fn(my_extension); let logs_processor = SharedService::new(service_fn(my_log_processor)); diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index a826a137..c2f813c3 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-custom-events" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-custom-events/src/main.rs b/examples/extension-custom-events/src/main.rs index 1d39e20f..1590e14a 100644 --- a/examples/extension-custom-events/src/main.rs +++ b/examples/extension-custom-events/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaEvent, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaEvent, NextEvent}; async fn my_extension(event: LambdaEvent) -> Result<(), Error> { match event.next { @@ -17,13 +17,7 @@ async fn my_extension(event: LambdaEvent) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); Extension::new() .with_events(&["SHUTDOWN"]) diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index c9ff789a..b51eae8e 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-custom-service" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-custom-service/src/main.rs b/examples/extension-custom-service/src/main.rs index ec8ca68f..9a97732c 100644 --- a/examples/extension-custom-service/src/main.rs +++ b/examples/extension-custom-service/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{run, Error, InvokeEvent, LambdaEvent, NextEvent, Service}; +use lambda_extension::{run, tracing, Error, InvokeEvent, LambdaEvent, NextEvent, Service}; use std::{ future::{ready, Future}, pin::Pin, @@ -34,13 +34,7 @@ impl Service for MyExtension { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(MyExtension::default()).await } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index 3706809d..ff1d10da 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -1,6 +1,6 @@ use anyhow::anyhow; use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; -use lambda_extension::{service_fn, Error, Extension, NextEvent}; +use lambda_extension::{service_fn, tracing, Error, Extension, NextEvent}; use serde::{Deserialize, Serialize}; use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; use tokio::sync::Mutex; @@ -81,6 +81,7 @@ impl EventHandler { #[tokio::main] async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); let (request_done_sender, request_done_receiver) = unbounded_channel::<()>(); let flush_extension = Arc::new(FlushExtension::new(request_done_receiver)); diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index d1983db8..dccc1ec0 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-logs-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-logs-basic/src/main.rs b/examples/extension-logs-basic/src/main.rs index 77065cca..4d2662e4 100644 --- a/examples/extension-logs-basic/src/main.rs +++ b/examples/extension-logs-basic/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaLog, LambdaLogRecord, SharedService}; use tracing::info; async fn handler(logs: Vec) -> Result<(), Error> { @@ -16,13 +16,7 @@ async fn handler(logs: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let logs_processor = SharedService::new(service_fn(handler)); diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index cbbe20f6..1b1eea0a 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-logs-custom-service" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-logs-custom-service/src/main.rs b/examples/extension-logs-custom-service/src/main.rs index ebe1330d..ad2db5bc 100644 --- a/examples/extension-logs-custom-service/src/main.rs +++ b/examples/extension-logs-custom-service/src/main.rs @@ -1,4 +1,4 @@ -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{ future::{ready, Future}, pin::Pin, @@ -8,7 +8,6 @@ use std::{ }, task::Poll, }; -use tracing::info; /// Custom log processor that increments a counter for each log record. /// @@ -44,8 +43,8 @@ impl Service> for MyLogsProcessor { let counter = self.counter.fetch_add(1, SeqCst); for log in logs { match log.record { - LambdaLogRecord::Function(record) => info!("[logs] [function] {}: {}", counter, record), - LambdaLogRecord::Extension(record) => info!("[logs] [extension] {}: {}", counter, record), + LambdaLogRecord::Function(record) => tracing::info!("[logs] [function] {}: {}", counter, record), + LambdaLogRecord::Extension(record) => tracing::info!("[logs] [extension] {}: {}", counter, record), _ => (), } } @@ -57,13 +56,7 @@ impl Service> for MyLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let logs_processor = SharedService::new(MyLogsProcessor::new()); diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index 0e056b1c..c6675e5a 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -3,17 +3,8 @@ name = "lambda-logs-firehose-extension" version = "0.1.0" edition = "2021" -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } tokio = { version = "1.17.0", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } aws-config = "0.13.0" aws-sdk-firehose = "0.13.0" - diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 8586e1a9..7871ce52 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -1,5 +1,5 @@ use aws_sdk_firehose::{model::Record, types::Blob, Client}; -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; +use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{future::Future, pin::Pin, task::Poll}; #[derive(Clone)] @@ -54,13 +54,7 @@ impl Service> for FirehoseLogsProcessor { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let config = aws_config::load_from_env().await; let logs_processor = SharedService::new(FirehoseLogsProcessor::new(Client::new(&config))); diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index 869b604d..1b8b1ba4 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -3,18 +3,7 @@ name = "extension-telemetry-basic" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda-extension = { path = "../../lambda-extension" } serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/extension-telemetry-basic/src/main.rs b/examples/extension-telemetry-basic/src/main.rs index 03974bf6..7159cf93 100644 --- a/examples/extension-telemetry-basic/src/main.rs +++ b/examples/extension-telemetry-basic/src/main.rs @@ -1,28 +1,27 @@ -use lambda_extension::{service_fn, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; -use tracing::info; +use lambda_extension::{service_fn, tracing, Error, Extension, LambdaTelemetry, LambdaTelemetryRecord, SharedService}; async fn handler(events: Vec) -> Result<(), Error> { for event in events { match event.record { - LambdaTelemetryRecord::Function(record) => info!("[logs] [function] {}", record), + LambdaTelemetryRecord::Function(record) => tracing::info!("[logs] [function] {}", record), LambdaTelemetryRecord::PlatformInitStart { initialization_type: _, phase: _, runtime_version: _, runtime_version_arn: _, - } => info!("[platform] Initialization started"), + } => tracing::info!("[platform] Initialization started"), LambdaTelemetryRecord::PlatformInitRuntimeDone { initialization_type: _, phase: _, status: _, error_type: _, spans: _, - } => info!("[platform] Initialization finished"), + } => tracing::info!("[platform] Initialization finished"), LambdaTelemetryRecord::PlatformStart { request_id, version: _, tracing: _, - } => info!("[platform] Handling of request {} started", request_id), + } => tracing::info!("[platform] Handling of request {} started", request_id), LambdaTelemetryRecord::PlatformRuntimeDone { request_id, status: _, @@ -30,7 +29,7 @@ async fn handler(events: Vec) -> Result<(), Error> { metrics: _, spans: _, tracing: _, - } => info!("[platform] Handling of request {} finished", request_id), + } => tracing::info!("[platform] Handling of request {} finished", request_id), _ => (), } } @@ -41,13 +40,7 @@ async fn handler(events: Vec) -> Result<(), Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let telemetry_processor = SharedService::new(service_fn(handler)); diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml index 5c3e806e..c757aa94 100644 --- a/examples/http-axum-apigw-authorizer/Cargo.toml +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -10,5 +10,3 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs index bb03de07..513a6cd8 100644 --- a/examples/http-axum-apigw-authorizer/src/main.rs +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -6,7 +6,7 @@ use axum::{ routing::get, Router, }; -use lambda_http::{run, Error, RequestExt}; +use lambda_http::{run, tracing, Error, RequestExt}; use serde_json::{json, Value}; use std::{collections::HashMap, env::set_var}; @@ -76,13 +76,7 @@ async fn main() -> Result<(), Error> { 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) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/extract-field", get(extract_field)) diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index 006a82ce..69366957 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum-diesel" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" bb8 = "0.8.0" @@ -18,8 +11,6 @@ diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } futures-util = "0.3.21" rustls = "0.20.8" rustls-native-certs = "0.6.2" diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs index c2f6b933..b340b44d 100755 --- a/examples/http-axum-diesel-ssl/src/main.rs +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -12,7 +12,7 @@ use axum::{ use bb8::Pool; use diesel::prelude::*; use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; -use lambda_http::{http::StatusCode, run, Error}; +use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; table! { @@ -98,22 +98,13 @@ fn internal_server_error(err: E) -> ServerError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Set up the database connection // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); - let mgr = AsyncDieselConnectionManager::::new_with_setup( - db_url, - establish_connection, - ); + let mgr = AsyncDieselConnectionManager::::new_with_setup(db_url, establish_connection); let pool = Pool::builder() .max_size(10) @@ -122,7 +113,7 @@ async fn main() -> Result<(), Error> { .idle_timeout(Some(Duration::from_secs(60 * 2))) .build(mgr) .await?; - + // Set up the API routes let posts_api = Router::new() .route("/", get(list_posts).post(create_post)) @@ -134,7 +125,6 @@ async fn main() -> Result<(), Error> { run(app).await } - fn establish_connection(config: &str) -> BoxFuture> { let fut = async { // We first set up the way we want rustls to work. diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 0366f32d..39fc813e 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum-diesel" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" bb8 = "0.8.0" @@ -19,5 +12,3 @@ lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.159" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index bb47152d..b7247be4 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -7,7 +7,7 @@ use axum::{ use bb8::Pool; use diesel::prelude::*; use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; -use lambda_http::{http::StatusCode, run, Error}; +use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; table! { @@ -93,13 +93,7 @@ fn internal_server_error(err: E) -> ServerError { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Set up the database connection let db_url = std::env::var("DATABASE_URL").expect("missing DATABASE_URL environment variable"); diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml index ea437052..228fc0ae 100644 --- a/examples/http-axum-middleware/Cargo.toml +++ b/examples/http-axum-middleware/Cargo.toml @@ -6,13 +6,8 @@ edition = "2021" [dependencies] axum = "0.7" lambda_http = { path = "../../lambda-http", default-features = false, features = [ - "apigw_rest", + "apigw_rest", "tracing" ] } lambda_runtime = { path = "../../lambda-runtime" } serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = [ - "fmt", -] } - diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs index 41c5bf4b..b1e92811 100644 --- a/examples/http-axum-middleware/src/main.rs +++ b/examples/http-axum-middleware/src/main.rs @@ -11,7 +11,7 @@ use axum::{response::Json, routing::post, Router}; use lambda_http::request::RequestContext::ApiGatewayV1; -use lambda_http::{run, Error}; +use lambda_http::{run, tracing, Error}; use serde_json::{json, Value}; // Sample middleware that logs the request id @@ -29,11 +29,7 @@ async fn handler_sample(body: Json) -> Json { #[tokio::main] async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - .with_target(false) - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/testStage/hello/world", post(handler_sample)) diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 5257bdb0..7ab3c0ec 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -3,13 +3,6 @@ name = "http-axum" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] axum = "0.7" lambda_http = { path = "../../lambda-http" } @@ -17,5 +10,3 @@ lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index ae7b0921..dcd5d154 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -14,7 +14,7 @@ use axum::{ routing::{get, post}, Router, }; -use lambda_http::{run, Error}; +use lambda_http::{run, tracing, Error}; use serde::{Deserialize, Serialize}; use serde_json::{json, Value}; use std::env::set_var; @@ -65,13 +65,7 @@ async fn main() -> Result<(), Error> { 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) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let app = Router::new() .route("/", get(root)) diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index 1a218330..c7a51507 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -3,18 +3,7 @@ name = "http-basic-lambda" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index 5794dc8b..d0e41561 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; /// This is the main body for the function. /// Write your code inside it. @@ -20,13 +20,7 @@ async fn function_handler(_event: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index 059a3f63..b8e51031 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -3,19 +3,8 @@ name = "http-cors" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.5", features = ["cors"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index e60fb441..ffac0a9e 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -1,18 +1,12 @@ use lambda_http::{ - http::Method, service_fn, tower::ServiceBuilder, Body, Error, IntoResponse, Request, RequestExt, Response, + http::Method, service_fn, tower::ServiceBuilder, tracing, Body, Error, IntoResponse, Request, RequestExt, Response, }; use tower_http::cors::{Any, CorsLayer}; #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Define a layer to inject CORS headers let cors_layer = CorsLayer::new() diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index be95f867..f2b8db98 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -3,24 +3,12 @@ name = "http-dynamodb" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] simple-error = "0.3.0" serde_json = "1.0.107" serde = { version = "1.0.189", features = ["derive"] } serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } aws-sdk-dynamodb = "0.33.0" aws-config = "0.56.1" tokio = { version = "1.33.0", features = ["macros"] } -tracing = { version = "0.1.40", features = ["log"] } -tracing-subscriber = { version = "0.3.17", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index b2e8af20..e5cbb2a3 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,8 +1,7 @@ -use aws_sdk_dynamodb::{Client}; -use lambda_http::{run, service_fn, Body, Error, Request, Response}; +use aws_sdk_dynamodb::Client; +use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; use serde::{Deserialize, Serialize}; use serde_dynamo::to_attribute_value; -use tracing::info; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { @@ -22,7 +21,7 @@ async fn handle_request(db_client: &Client, event: Request) -> Result Result Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); //Get config from environment. let config = aws_config::load_from_env().await; @@ -93,7 +86,7 @@ pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Er .item("first_name", first_av) .item("last_name", last_av); - info!("adding item to DynamoDB"); + tracing::info!("adding item to DynamoDB"); let _resp = request.send().await?; diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 7aeb1189..2cadda95 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -3,18 +3,7 @@ name = "http-query-parameters" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index e189d12d..1f7110a5 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{run, service_fn, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{run, service_fn, tracing, Error, IntoResponse, Request, RequestExt, Response}; /// This is the main body for the function. /// Write your code inside it. @@ -23,13 +23,7 @@ async fn function_handler(event: Request) -> Result { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); run(service_fn(function_handler)).await } diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index f4060428..f6c56526 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -3,18 +3,7 @@ name = "http-raw-path" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-raw-path/src/main.rs b/examples/http-raw-path/src/main.rs index 7fa6e6d5..70b4df4d 100644 --- a/examples/http-raw-path/src/main.rs +++ b/examples/http-raw-path/src/main.rs @@ -1,15 +1,9 @@ -use lambda_http::{service_fn, Error, IntoResponse, Request, RequestExt}; +use lambda_http::{service_fn, tracing, Error, IntoResponse, Request, RequestExt}; #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); lambda_http::run(service_fn(func)).await?; Ok(()) diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 207f253b..923ceecc 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -3,18 +3,7 @@ name = "http-shared-resource" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } - - diff --git a/examples/http-shared-resource/src/main.rs b/examples/http-shared-resource/src/main.rs index d76ccec4..cff9e785 100644 --- a/examples/http-shared-resource/src/main.rs +++ b/examples/http-shared-resource/src/main.rs @@ -1,4 +1,4 @@ -use lambda_http::{service_fn, Body, Error, IntoResponse, Request, RequestExt, Response}; +use lambda_http::{service_fn, tracing, Body, Error, IntoResponse, Request, RequestExt, Response}; struct SharedClient { name: &'static str, @@ -13,13 +13,7 @@ impl SharedClient { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); // Create the "client" and a reference to it, so that we can pass this into the handler closure below. let shared_client = SharedClient { diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 0b0c46a9..36389f0e 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -3,17 +3,8 @@ name = "http-tower-trace" version = "0.1.0" edition = "2021" - -# Use cargo-edit(https://github.com/killercup/cargo-edit#installation) -# to manage dependencies. -# Running `cargo add DEPENDENCY_NAME` will -# add the latest version of a dependency to the list, -# and it will keep the alphabetic ordering for you. - [dependencies] lambda_http = { path = "../../lambda-http" } lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } tower-http = { version = "0.5", features = ["trace"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index 072f8256..df2b9007 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -1,7 +1,7 @@ +use lambda_http::tracing::{self, Level}; use lambda_http::{run, tower::ServiceBuilder, Error}; use lambda_http::{Request, Response}; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; -use tracing::Level; async fn handler(_req: Request) -> Result, Error> { Ok(Response::new("Success".into())) @@ -10,13 +10,7 @@ async fn handler(_req: Request) -> Result, Error> { #[tokio::main] async fn main() -> Result<(), Error> { // required to enable CloudWatch error logging by the runtime - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disable printing the name of the module in every log line. - .with_target(false) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); + tracing::init_default_subscriber(); let layer = TraceLayer::new_for_http() .on_request(DefaultOnRequest::new().level(Level::INFO)) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index ba0898a3..aa11f1cb 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -13,6 +13,10 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "README.md" +[features] +default = ["tracing"] +tracing = ["lambda_runtime_api_client/tracing"] + [dependencies] async-stream = "0.3" bytes = { workspace = true } @@ -24,7 +28,6 @@ hyper-util = { workspace = true } lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" -tracing = { version = "0.1", features = ["log"] } tokio = { version = "1.0", features = [ "macros", "io-util", @@ -33,3 +36,4 @@ tokio = { version = "1.0", features = [ ] } tokio-stream = "0.1.2" tower = { version = "0.4", features = ["make", "util"] } +tracing = { version = "0.1", features = ["log"] } diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 796eb3ef..81c16337 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -23,6 +23,10 @@ pub use telemetry::*; /// Include several request builders to interact with the Extension API. pub mod requests; +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime_api_client::tracing; + /// Execute the given events processor pub async fn run(events_processor: E) -> Result<(), Error> where diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9facc13d..8617e7ce 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -16,12 +16,13 @@ categories = ["web-programming::http-server"] readme = "README.md" [features] -default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb"] +default = ["apigw_rest", "apigw_http", "apigw_websockets", "alb", "tracing"] apigw_rest = [] apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] +tracing = ["lambda_runtime/tracing"] [dependencies] base64 = { workspace = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index bc9e753d..265f5ef0 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -65,8 +65,10 @@ extern crate maplit; pub use http::{self, Response}; -use lambda_runtime::LambdaEvent; -pub use lambda_runtime::{self, service_fn, tower, Context, Error, Service}; +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime::tracing; +pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service}; use request::RequestFuture; use response::ResponseFuture; diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 12b26043..2fa13207 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -13,6 +13,10 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "README.md" +[features] +default = ["tracing"] +tracing = ["dep:tracing", "dep:tracing-subscriber"] + [dependencies] bytes = { workspace = true } futures-channel = { workspace = true } @@ -30,3 +34,5 @@ hyper-util = { workspace = true, features = [ tower = { workspace = true, features = ["util"] } tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } +tracing = { version = "0.1", features = ["log"], optional = true } +tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index ec7418ba..8e52d416 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -17,6 +17,9 @@ mod error; pub use error::*; pub mod body; +#[cfg(feature = "tracing")] +pub mod tracing; + /// API client to interact with the AWS Lambda Runtime API. #[derive(Debug)] pub struct Client { diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs new file mode 100644 index 00000000..babc817c --- /dev/null +++ b/lambda-runtime-api-client/src/tracing.rs @@ -0,0 +1,41 @@ +//! This module provides primitives to work with `tracing` +//! and `tracing-subscriber` in Lambda functions. +//! +//! The `tracing` and `tracing-subscriber` crates are re-exported +//! so you don't have to include them as direct dependencies in +//! your projects. + +use std::{env, str::FromStr}; + +use subscriber::filter::{EnvFilter, LevelFilter}; +/// Re-export the `tracing` crate to have access to tracing macros +/// like `info!`, `debug!`, `trace!` and so on. +pub use tracing::*; + +/// Re-export the `tracing-subscriber` crate to build your own subscribers. +pub use tracing_subscriber as subscriber; + +/// Initialize `tracing-subscriber` with default options. +/// The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. +/// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// if they're configured for your function. +/// By default, the log level to emit events is `INFO`. +pub fn init_default_subscriber() { + let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); + let log_level = Level::from_str(&env::var("AWS_LAMBDA_LOG_LEVEL").unwrap_or_default()).unwrap_or(Level::INFO); + + let collector = tracing_subscriber::fmt() + .with_target(false) + .without_time() + .with_env_filter( + EnvFilter::builder() + .with_default_directive(LevelFilter::from_level(log_level).into()) + .from_env_lossy(), + ); + + if log_format.eq_ignore_ascii_case("json") { + collector.json().init() + } else { + collector.init() + } +} diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 94a3c201..1a34a67d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -14,8 +14,8 @@ keywords = ["AWS", "Lambda", "API"] readme = "../README.md" [features] -default = ["simulated"] -simulated = [] +default = ["tracing"] +tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index b4964954..3fe56b03 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,6 +7,7 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. +use ::tracing::{error, trace, Instrument}; use bytes::Bytes; use futures::FutureExt; use http_body_util::BodyExt; @@ -24,12 +25,16 @@ use std::{ use tokio_stream::{Stream, StreamExt}; pub use tower::{self, service_fn, Service}; use tower::{util::ServiceFn, ServiceExt}; -use tracing::{error, trace, Instrument}; mod deserializer; mod requests; /// Utilities for Lambda Streaming functions. pub mod streaming; + +/// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. +#[cfg(feature = "tracing")] +pub use lambda_runtime_api_client::tracing; + /// Types available to a Lambda function. mod types; From e0e95e61cba0a8a695d4766d9215cbe839caf3a7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 25 Feb 2024 20:03:13 -0800 Subject: [PATCH 096/211] Release runtime 0.10 and events 0.15 (#832) - Support advanced logging controls. - Support CloudWatch SNS notifications. Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 8 ++++---- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 3010aedc..2144a9a9 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.14.0" +version = "0.15.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index aa11f1cb..a7523e17 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "David Calavera ", @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8617e7ce..e05347ec 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.9.3" +version = "0.10.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.9.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.10.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -46,14 +46,14 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.14.0" +version = "0.15.0" default-features = false features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 2fa13207..819d5ed7 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.9.0" +version = "0.10.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1a34a67d..d61e5594 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.9.2" +version = "0.10.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.9", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" From 29e148a1b69c4e869008ba7f1cbefc46ebff851e Mon Sep 17 00:00:00 2001 From: Benjamen Pyle Date: Mon, 4 Mar 2024 14:55:09 -0600 Subject: [PATCH 097/211] Add struct for CodeDeployLifecycleEvent (#835) CodeDeploy Hooks Lifecycle Event support * Added struct * Added unit test --- lambda-events/src/event/codedeploy/mod.rs | 21 +++++++++++++++++++ .../example-codedeploy-lifecycle-event.json | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 896f509f..d51bf8aa 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -65,10 +65,31 @@ pub struct CodeDeployEventDetail { pub deployment_group: Option, } +#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] +#[serde(rename_all = "PascalCase")] +pub struct CodeDeployLifecycleEvent { + pub deployment_id: String, + pub lifecycle_event_hook_execution_id: String, +} + #[cfg(test)] mod test { use super::*; + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_lifecycle_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-lifecycle-event.json"); + let parsed: CodeDeployLifecycleEvent = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.deployment_id, "d-deploymentId".to_string()); + assert_eq!(parsed.lifecycle_event_hook_execution_id, "eyJlbmNyeXB0ZWREYXRhIjoiY3VHU2NjdkJXUTJQUENVd2dkYUNGRVg0dWlpME9UWVdHTVhZcDRXVW5LYUVKc21EaUFPMkNLNXMwMWFrNDlYVStlbXdRb29xS3NJTUNVQ3RYRGFZSXc1VTFwUllvMDhmMzdlbDZFeDVVdjZrNFc0eU5waGh6YTRvdkNWcmVveVR6OWdERlM2SmlIYW1TZz09IiwiaXZQYXJhbWV0ZXJTcGVjIjoiTm1ZNFR6RzZxQVhHamhhLyIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ==".to_string()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployLifecycleEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "codedeploy")] fn example_codedeploy_deployment_event() { diff --git a/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json b/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json new file mode 100644 index 00000000..41baa76c --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-lifecycle-event.json @@ -0,0 +1,5 @@ +{ + "DeploymentId": "d-deploymentId", + "LifecycleEventHookExecutionId": "eyJlbmNyeXB0ZWREYXRhIjoiY3VHU2NjdkJXUTJQUENVd2dkYUNGRVg0dWlpME9UWVdHTVhZcDRXVW5LYUVKc21EaUFPMkNLNXMwMWFrNDlYVStlbXdRb29xS3NJTUNVQ3RYRGFZSXc1VTFwUllvMDhmMzdlbDZFeDVVdjZrNFc0eU5waGh6YTRvdkNWcmVveVR6OWdERlM2SmlIYW1TZz09IiwiaXZQYXJhbWV0ZXJTcGVjIjoiTm1ZNFR6RzZxQVhHamhhLyIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ==" +} + From f610ff6a377de8e7fc814df741def896503dcd97 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Mon, 11 Mar 2024 16:35:57 -0700 Subject: [PATCH 098/211] Add CloudFormationCustomResourceResponse struct. (#838) Co-authored-by: Michael Wallace --- lambda-events/src/event/cloudformation/mod.rs | 30 +++++++++++++++++++ ...oudformation-custom-resource-response.json | 14 +++++++++ 2 files changed, 44 insertions(+) create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 0d3f816c..3123d8af 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -1,5 +1,6 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; +use std::collections::HashMap; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] @@ -75,6 +76,26 @@ where pub resource_properties: P2, } +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CloudFormationCustomResourceResponse { + pub status: CloudFormationCustomResourceResponseStatus, + pub reason: Option, + pub physical_resource_id: String, + pub stack_id: String, + pub request_id: String, + pub logical_resource_id: String, + pub no_echo: bool, + pub data: HashMap, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum CloudFormationCustomResourceResponseStatus { + Success, + Failed, +} + #[cfg(test)] mod test { use std::collections::HashMap; @@ -136,4 +157,13 @@ mod test { let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + fn example_cloudformation_custom_resource_response() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-response.json"); + let parsed: CloudFormationCustomResourceResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudFormationCustomResourceResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json new file mode 100644 index 00000000..86d018ad --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-response.json @@ -0,0 +1,14 @@ +{ + "Status": "FAILED", + "Reason": "This is a test failure.", + "PhysicalResourceId": "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "StackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "RequestId": "49347ca5-c603-44e5-a34b-10cf1854a887", + "LogicalResourceId": "CustomResource", + "NoEcho": false, + "Data": { + "Key1": "a", + "Key2": "b", + "Key3": "c" + } +} \ No newline at end of file From 240ae6d86f7fc2c7ce7402724f65fe9158d5d3a0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 11 Mar 2024 19:15:44 -0700 Subject: [PATCH 099/211] Stop using deprecated chrono's api. (#839) --- Cargo.toml | 1 + lambda-events/Cargo.toml | 2 +- lambda-events/src/encodings/time.rs | 40 +++++++++++++++-------------- lambda-extension/Cargo.toml | 2 +- lambda-extension/src/logs.rs | 4 +-- lambda-extension/src/telemetry.rs | 12 ++++----- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 51d57ff4..cba3ba3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ exclude = ["examples"] [workspace.dependencies] base64 = "0.21" bytes = "1" +chrono = "0.4.35" futures = "0.3" futures-channel = "0.3" futures-util = "0.3" diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 2144a9a9..eb5d9c31 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -18,7 +18,7 @@ edition = "2021" [dependencies] base64 = { workspace = true } bytes = { workspace = true, features = ["serde"], optional = true } -chrono = { version = "0.4.31", default-features = false, features = [ +chrono = { workspace = true, default-features = false, features = [ "clock", "serde", "std", diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 203aff75..6d77b5cf 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -1,4 +1,4 @@ -use chrono::{DateTime, Duration, TimeZone, Utc}; +use chrono::{DateTime, TimeDelta, TimeZone, Utc}; use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError}, @@ -55,11 +55,11 @@ impl DerefMut for SecondTimestamp { pub struct SecondDuration( #[serde(deserialize_with = "deserialize_duration_seconds")] #[serde(serialize_with = "serialize_duration_seconds")] - pub Duration, + pub TimeDelta, ); impl Deref for SecondDuration { - type Target = Duration; + type Target = TimeDelta; fn deref(&self) -> &Self::Target { &self.0 @@ -77,11 +77,11 @@ impl DerefMut for SecondDuration { pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] #[serde(serialize_with = "serialize_duration_minutes")] - pub Duration, + pub TimeDelta, ); impl Deref for MinuteDuration { - type Target = Duration; + type Target = TimeDelta; fn deref(&self) -> &Self::Target { &self.0 @@ -144,7 +144,7 @@ where .ok_or_else(|| D::Error::custom("invalid timestamp")) } -fn serialize_duration_seconds(duration: &Duration, serializer: S) -> Result +fn serialize_duration_seconds(duration: &TimeDelta, serializer: S) -> Result where S: Serializer, { @@ -153,15 +153,16 @@ where serializer.serialize_i64(seconds) } -fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result +fn deserialize_duration_seconds<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let seconds = f64::deserialize(deserializer)?; - Ok(Duration::seconds(seconds as i64)) + TimeDelta::try_seconds(seconds as i64) + .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{}`", seconds))) } -fn serialize_duration_minutes(duration: &Duration, serializer: S) -> Result +fn serialize_duration_minutes(duration: &TimeDelta, serializer: S) -> Result where S: Serializer, { @@ -170,12 +171,13 @@ where serializer.serialize_i64(minutes) } -fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result +fn deserialize_duration_minutes<'de, D>(deserializer: D) -> Result where D: Deserializer<'de>, { let minutes = f64::deserialize(deserializer)?; - Ok(Duration::minutes(minutes as i64)) + TimeDelta::try_minutes(minutes as i64) + .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{}`", minutes))) } fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> @@ -291,10 +293,10 @@ mod test { #[derive(Deserialize)] struct Test { #[serde(deserialize_with = "deserialize_duration_seconds")] - v: Duration, + v: TimeDelta, } - let expected = Duration::seconds(36); + let expected = TimeDelta::try_seconds(36).unwrap(); let data = serde_json::json!({ "v": 36, @@ -314,10 +316,10 @@ mod test { #[derive(Serialize)] struct Test { #[serde(serialize_with = "serialize_duration_seconds")] - v: Duration, + v: TimeDelta, } let instance = Test { - v: Duration::seconds(36), + v: TimeDelta::try_seconds(36).unwrap(), }; let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":36}"#)); @@ -328,10 +330,10 @@ mod test { #[derive(Deserialize)] struct Test { #[serde(deserialize_with = "deserialize_duration_minutes")] - v: Duration, + v: TimeDelta, } - let expected = Duration::minutes(36); + let expected = TimeDelta::try_minutes(36).unwrap(); let data = serde_json::json!({ "v": 36, @@ -351,10 +353,10 @@ mod test { #[derive(Serialize)] struct Test { #[serde(serialize_with = "serialize_duration_minutes")] - v: Duration, + v: TimeDelta, } let instance = Test { - v: Duration::minutes(36), + v: TimeDelta::try_minutes(36).unwrap(), }; let encoded = serde_json::to_string(&instance).unwrap(); assert_eq!(encoded, String::from(r#"{"v":36}"#)); diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index a7523e17..fba19357 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -20,7 +20,7 @@ tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" bytes = { workspace = true } -chrono = { version = "0.4", features = ["serde"] } +chrono = { workspace = true, features = ["serde"] } http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index 4d1948a0..c3b0cda2 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -233,7 +233,7 @@ where #[cfg(test)] mod tests { use super::*; - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; #[test] fn deserialize_full() { @@ -242,7 +242,7 @@ mod tests { time: Utc .with_ymd_and_hms(2020, 8, 20, 12, 31, 32) .unwrap() - .checked_add_signed(Duration::milliseconds(123)) + .checked_add_signed(TimeDelta::try_milliseconds(123).unwrap()) .unwrap(), record: LambdaLogRecord::Function("hello world".to_string()), }; diff --git a/lambda-extension/src/telemetry.rs b/lambda-extension/src/telemetry.rs index cfb4dde2..a7760892 100644 --- a/lambda-extension/src/telemetry.rs +++ b/lambda-extension/src/telemetry.rs @@ -316,7 +316,7 @@ where #[cfg(test)] mod deserialization_tests { use super::*; - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; macro_rules! deserialize_tests { ($($name:ident: $value:expr,)*) => { @@ -385,7 +385,7 @@ mod deserialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) .unwrap() - .checked_add_signed(Duration::milliseconds(165)) + .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap()) .unwrap(), duration_ms: 2598.0 }, @@ -394,7 +394,7 @@ mod deserialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) .unwrap() - .checked_add_signed(Duration::milliseconds(763)) + .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap()) .unwrap(), duration_ms: 0.0 }, @@ -473,7 +473,7 @@ mod deserialization_tests { #[cfg(test)] mod serialization_tests { - use chrono::{Duration, TimeZone}; + use chrono::{TimeDelta, TimeZone}; use super::*; macro_rules! serialize_tests { @@ -557,7 +557,7 @@ mod serialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 3) .unwrap() - .checked_add_signed(Duration::milliseconds(165)) + .checked_add_signed(TimeDelta::try_milliseconds(165).unwrap()) .unwrap(), duration_ms: 2598.0 }, @@ -566,7 +566,7 @@ mod serialization_tests { start: Utc .with_ymd_and_hms(2022, 10, 21, 14, 5, 5) .unwrap() - .checked_add_signed(Duration::milliseconds(763)) + .checked_add_signed(TimeDelta::try_milliseconds(763).unwrap()) .unwrap(), duration_ms: 0.0 }, From 5b399cfc90da0a6d9b92d8707108c13457d72546 Mon Sep 17 00:00:00 2001 From: FalkWoldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Tue, 12 Mar 2024 20:48:07 +0100 Subject: [PATCH 100/211] Fix typo (#840) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a5920b7..1c84ae43 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. -By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subsriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: +By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subscriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: ```rust use lambda_runtime::{run, service_fn, tracing, Error}; From aeee3b6e8282aec74f589beb1af5166c9937f9b2 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Wed, 13 Mar 2024 15:54:20 +0000 Subject: [PATCH 101/211] feat: Add default Value type for EventBridgeEvent detail (#843) --- lambda-events/src/event/eventbridge/mod.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index ed9bf447..49743e0a 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,15 +1,20 @@ use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; +use serde_json::Value; /// Parse EventBridge events. /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// /// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] -pub struct EventBridgeEvent { +pub struct EventBridgeEvent +where + T1: Serialize, + T1: DeserializeOwned, +{ #[serde(default)] pub version: Option, #[serde(default)] @@ -24,8 +29,8 @@ pub struct EventBridgeEvent { pub region: Option, #[serde(default)] pub resources: Option>, - #[serde(bound(deserialize = "T: DeserializeOwned"))] - pub detail: T, + #[serde(bound = "")] + pub detail: T1, } #[cfg(test)] From af639e86c773001f91e55c50511ef57b73c69040 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Thu, 14 Mar 2024 01:59:03 +0000 Subject: [PATCH 102/211] Chore: Adds eventbridge schedule event data-example fixture (#842) * Chore: Adds eventbridge schedule event data-example fixture * chore: added test for the new Scheduled event --- lambda-events/src/event/eventbridge/mod.rs | 13 +++++++++++++ .../src/fixtures/example-eventbridge-schedule.json | 13 +++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 lambda-events/src/fixtures/example-eventbridge-schedule.json diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 49743e0a..990a853d 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -58,4 +58,17 @@ mod test { let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + fn example_eventbridge_schedule_event() { + let data = include_bytes!("../../fixtures/example-eventbridge-schedule.json"); + let parsed: EventBridgeEvent = serde_json::from_slice(data).unwrap(); + + assert_eq!("aws.events", parsed.source); + assert_eq!("Scheduled Event", parsed.detail_type); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventBridgeEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-eventbridge-schedule.json b/lambda-events/src/fixtures/example-eventbridge-schedule.json new file mode 100644 index 00000000..602f83d2 --- /dev/null +++ b/lambda-events/src/fixtures/example-eventbridge-schedule.json @@ -0,0 +1,13 @@ +{ + "version": "0", + "id": "53dc4d37-cffa-4f76-80c9-8b7d4a4d2eaa", + "detail-type": "Scheduled Event", + "source": "aws.events", + "account": "123456789012", + "time": "2015-10-08T16:53:06Z", + "region": "us-east-1", + "resources": [ + "arn:aws:events:us-east-1:123456789012:rule/my-scheduled-rule" + ], + "detail": {} +} From bae37bd22c943642f6a9cb955ced5ab4aa8462e1 Mon Sep 17 00:00:00 2001 From: noid11 <12472231+noid11@users.noreply.github.com> Date: Mon, 18 Mar 2024 02:17:53 +0900 Subject: [PATCH 103/211] fix: Correct Markdown format for Tracing link in README (#844) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1c84ae43..8f4838c5 100644 --- a/README.md +++ b/README.md @@ -374,7 +374,7 @@ Lambdas can be run and debugged locally using a special [Lambda debug proxy](htt ## Tracing and Logging -The Rust Runtime for Lambda integrates with the (Tracing)[https://tracing.rs] libraries to provide tracing and logging. +The Rust Runtime for Lambda integrates with the [Tracing](https://tracing.rs) libraries to provide tracing and logging. By default, the runtime emits `tracing` events that you can collect via `tracing-subscriber`. It also enabled a feature called `tracing` that exposes a default subscriber with sensible options to send logging information to AWS CloudWatch. Follow the next example that shows how to enable the default subscriber: From 731d201f4b82ec553c0f1e52c6fe6625aca2d82a Mon Sep 17 00:00:00 2001 From: Oliver Borchert Date: Fri, 22 Mar 2024 03:49:25 +0100 Subject: [PATCH 104/211] feat: Implement RFC for layering of runtime (#845) * feat: Implement RFC for layering of runtime * Add example * Fix compilation errors * Remove Send and 'static * Fix ci * Reduce diff * Implement review comments --- .github/workflows/build-events.yml | 6 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- Cargo.toml | 3 +- README.md | 2 +- examples/opentelemetry-tracing/Cargo.toml | 36 ++ examples/opentelemetry-tracing/src/lib.rs | 113 ++++ examples/opentelemetry-tracing/src/main.rs | 34 ++ lambda-events/src/custom_serde/mod.rs | 8 +- .../src/event/dynamodb/attributes.rs | 2 +- lambda-runtime-api-client/src/lib.rs | 15 +- lambda-runtime/Cargo.toml | 2 + lambda-runtime/src/layers/api_client.rs | 85 ++++ lambda-runtime/src/layers/api_response.rs | 173 +++++++ lambda-runtime/src/layers/mod.rs | 12 + lambda-runtime/src/layers/panic.rs | 118 +++++ lambda-runtime/src/layers/trace.rs | 68 +++ lambda-runtime/src/lib.rs | 413 +-------------- lambda-runtime/src/runtime.rs | 481 ++++++++++++++++++ lambda-runtime/src/types.rs | 20 - 20 files changed, 1160 insertions(+), 435 deletions(-) create mode 100644 examples/opentelemetry-tracing/Cargo.toml create mode 100644 examples/opentelemetry-tracing/src/lib.rs create mode 100644 examples/opentelemetry-tracing/src/main.rs create mode 100644 lambda-runtime/src/layers/api_client.rs create mode 100644 lambda-runtime/src/layers/api_response.rs create mode 100644 lambda-runtime/src/layers/mod.rs create mode 100644 lambda-runtime/src/layers/panic.rs create mode 100644 lambda-runtime/src/layers/trace.rs create mode 100644 lambda-runtime/src/runtime.rs diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index d9f5c72a..3026f1ac 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -3,10 +3,10 @@ name: Check Lambda Events on: push: paths: - - 'lambda-events/**' + - "lambda-events/**" pull_request: paths: - - 'lambda-events/**' + - "lambda-events/**" jobs: build: @@ -14,7 +14,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index d9bcc989..d09b08c0 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -18,7 +18,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 25cd83ec..a52927b5 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -19,7 +19,7 @@ jobs: strategy: matrix: toolchain: - - "1.66.0" # Current MSRV + - "1.70.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index cba3ba3b..09b046e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,7 +6,7 @@ members = [ "lambda-runtime-api-client", "lambda-runtime", "lambda-extension", - "lambda-events" + "lambda-events", ] exclude = ["examples"] @@ -26,4 +26,5 @@ hyper = "1.0" hyper-util = "0.1.1" pin-project-lite = "0.2" tower = "0.4" +tower-layer = "0.3" tower-service = "0.3" diff --git a/README.md b/README.md index 8f4838c5..331635d2 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ This will make your function compile much faster. ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.66, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml new file mode 100644 index 00000000..27a778b5 --- /dev/null +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "opentelemetry-tracing" +version = "0.1.0" +edition = "2021" + +[dependencies] +# Library dependencies +lambda_runtime = { path = "../../lambda-runtime" } +pin-project = "1" +opentelemetry-semantic-conventions = "0.14" +tower = "0.4" +tracing = "0.1" + +# Binary dependencies +opentelemetry = { version = "0.22", optional = true } +opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"], optional = true } +opentelemetry-stdout = { version = "0.3", features = ["trace"], optional = true } +serde_json = { version = "1.0", optional = true } +tokio = { version = "1", optional = true } +tracing-opentelemetry = { version = "0.23", optional = true } +tracing-subscriber = { version = "0.3", optional = true } + +[features] +build-binary = [ + "opentelemetry", + "opentelemetry_sdk", + "opentelemetry-stdout", + "serde_json", + "tokio", + "tracing-opentelemetry", + "tracing-subscriber", +] + +[[bin]] +name = "opentelemetry-tracing" +required-features = ["build-binary"] diff --git a/examples/opentelemetry-tracing/src/lib.rs b/examples/opentelemetry-tracing/src/lib.rs new file mode 100644 index 00000000..82f12a16 --- /dev/null +++ b/examples/opentelemetry-tracing/src/lib.rs @@ -0,0 +1,113 @@ +use std::future::Future; +use std::pin::Pin; +use std::task; + +use lambda_runtime::LambdaInvocation; +use opentelemetry_semantic_conventions::trace as traceconv; +use pin_project::pin_project; +use tower::{Layer, Service}; +use tracing::instrument::Instrumented; +use tracing::Instrument; + +/// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts +/// a function to flush OpenTelemetry after the end of the invocation. +pub struct OpenTelemetryLayer { + flush_fn: F, +} + +impl OpenTelemetryLayer +where + F: Fn() + Clone, +{ + pub fn new(flush_fn: F) -> Self { + Self { flush_fn } + } +} + +impl Layer for OpenTelemetryLayer +where + F: Fn() + Clone, +{ + type Service = OpenTelemetryService; + + fn layer(&self, inner: S) -> Self::Service { + OpenTelemetryService { + inner, + flush_fn: self.flush_fn.clone(), + coldstart: true, + } + } +} + +/// Tower service created by [OpenTelemetryLayer]. +pub struct OpenTelemetryService { + inner: S, + flush_fn: F, + coldstart: bool, +} + +impl Service for OpenTelemetryService +where + S: Service, + F: Fn() + Clone, +{ + type Error = S::Error; + type Response = (); + type Future = OpenTelemetryFuture, F>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let span = tracing::info_span!( + "Lambda function invocation", + "otel.name" = req.context.env_config.function_name, + { traceconv::FAAS_TRIGGER } = "http", + { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, + { traceconv::FAAS_COLDSTART } = self.coldstart + ); + + // After the first execution, we can set 'coldstart' to false + self.coldstart = false; + + let fut = self.inner.call(req).instrument(span); + OpenTelemetryFuture { + future: Some(fut), + flush_fn: self.flush_fn.clone(), + } + } +} + +/// Future created by [OpenTelemetryService]. +#[pin_project] +pub struct OpenTelemetryFuture { + #[pin] + future: Option, + flush_fn: F, +} + +impl Future for OpenTelemetryFuture +where + Fut: Future, + F: Fn(), +{ + type Output = Fut::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + // First, try to get the ready value of the future + let ready = task::ready!(self + .as_mut() + .project() + .future + .as_pin_mut() + .expect("future polled after completion") + .poll(cx)); + + // If we got the ready value, we first drop the future: this ensures that the + // OpenTelemetry span attached to it is closed and included in the subsequent flush. + Pin::set(&mut self.as_mut().project().future, None); + (self.project().flush_fn)(); + task::Poll::Ready(ready) + } +} diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs new file mode 100644 index 00000000..68038366 --- /dev/null +++ b/examples/opentelemetry-tracing/src/main.rs @@ -0,0 +1,34 @@ +use lambda_runtime::{LambdaEvent, Runtime}; +use opentelemetry::trace::TracerProvider; +use opentelemetry_sdk::{runtime, trace}; +use opentelemetry_tracing::OpenTelemetryLayer; +use tower::{service_fn, BoxError}; +use tracing_subscriber::prelude::*; + +async fn echo(event: LambdaEvent) -> Result { + Ok(event.payload) +} + +#[tokio::main] +async fn main() -> Result<(), BoxError> { + // Set up OpenTelemetry tracer provider that writes spans to stdout for debugging purposes + let exporter = opentelemetry_stdout::SpanExporter::default(); + let tracer_provider = trace::TracerProvider::builder() + .with_batch_exporter(exporter, runtime::Tokio) + .build(); + + // Set up link between OpenTelemetry and tracing crate + tracing_subscriber::registry() + .with(tracing_opentelemetry::OpenTelemetryLayer::new( + tracer_provider.tracer("my-app"), + )) + .init(); + + // Initialize the Lambda runtime and add OpenTelemetry tracing + let runtime = Runtime::new(service_fn(echo)).layer(OpenTelemetryLayer::new(|| { + // Make sure that the trace is exported before the Lambda runtime is frozen + tracer_provider.force_flush(); + })); + runtime.run().await?; + Ok(()) +} diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 46d121d1..030cb5b3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -177,18 +177,18 @@ mod test { let test = r#"{"v": null}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); let test = r#"{}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); let test = r#"{"v": true}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(true, decoded.v); + assert!(decoded.v); let test = r#"{"v": false}"#; let decoded: Test = serde_json::from_str(test).unwrap(); - assert_eq!(false, decoded.v); + assert!(!decoded.v); } } diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index aad2cd4b..e1a42c83 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -83,7 +83,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { - AttributeValue::Bool(b) => assert_eq!(true, b), + AttributeValue::Bool(b) => assert!(b), other => panic!("unexpected value {:?}", other), } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 8e52d416..4315dfd7 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -4,10 +4,11 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. +use futures_util::{future::BoxFuture, FutureExt, TryFutureExt}; use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; use hyper::body::Incoming; use hyper_util::client::legacy::connect::HttpConnector; -use std::{convert::TryInto, fmt::Debug}; +use std::{convert::TryInto, fmt::Debug, future}; const USER_AGENT_HEADER: &str = "User-Agent"; const DEFAULT_USER_AGENT: &str = concat!("aws-lambda-rust/", env!("CARGO_PKG_VERSION")); @@ -42,9 +43,15 @@ impl Client { impl Client { /// Send a given request to the Runtime API. /// Use the client's base URI to ensure the API endpoint is correct. - pub async fn call(&self, req: Request) -> Result, BoxError> { - let req = self.set_origin(req)?; - self.client.request(req).await.map_err(Into::into) + pub fn call(&self, req: Request) -> BoxFuture<'static, Result, BoxError>> { + // NOTE: This method returns a boxed future such that the future has a static lifetime. + // Due to limitations around the Rust async implementation as of Mar 2024, this is + // required to minimize constraints on the handler passed to [lambda_runtime::run]. + let req = match self.set_origin(req) { + Ok(req) => req, + Err(err) => return future::ready(Err(err)).boxed(), + }; + self.client.request(req).map_err(Into::into).boxed() } /// Create a new client with a given base URI and HTTP connector. diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d61e5594..d9eca35a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -37,6 +37,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" @@ -48,6 +49,7 @@ tokio = { version = "1.0", features = [ ] } tokio-stream = "0.1.2" tower = { workspace = true, features = ["util"] } +tower-layer = { workspace = true } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] diff --git a/lambda-runtime/src/layers/api_client.rs b/lambda-runtime/src/layers/api_client.rs new file mode 100644 index 00000000..b6d9acf8 --- /dev/null +++ b/lambda-runtime/src/layers/api_client.rs @@ -0,0 +1,85 @@ +use crate::LambdaInvocation; +use futures::{future::BoxFuture, ready, FutureExt, TryFutureExt}; +use hyper::body::Incoming; +use lambda_runtime_api_client::{body::Body, BoxError, Client}; +use pin_project::pin_project; +use std::future::Future; +use std::pin::Pin; +use std::sync::Arc; +use std::task; +use tower::Service; +use tracing::error; + +/// Tower service that sends a Lambda Runtime API response to the Lambda Runtime HTTP API using +/// a previously initialized client. +/// +/// This type is only meant for internal use in the Lambda runtime crate. It neither augments the +/// inner service's request type nor its error type. However, this service returns an empty +/// response `()` as the Lambda request has been completed. +pub struct RuntimeApiClientService { + inner: S, + client: Arc, +} + +impl RuntimeApiClientService { + pub fn new(inner: S, client: Arc) -> Self { + Self { inner, client } + } +} + +impl Service for RuntimeApiClientService +where + S: Service, + S::Future: Future, BoxError>>, +{ + type Response = (); + type Error = S::Error; + type Future = RuntimeApiClientFuture; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let request_fut = self.inner.call(req); + let client = self.client.clone(); + RuntimeApiClientFuture::First(request_fut, client) + } +} + +#[pin_project(project = RuntimeApiClientFutureProj)] +pub enum RuntimeApiClientFuture { + First(#[pin] F, Arc), + Second(#[pin] BoxFuture<'static, Result, BoxError>>), +} + +impl Future for RuntimeApiClientFuture +where + F: Future, BoxError>>, +{ + type Output = Result<(), BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + // NOTE: We loop here to directly poll the second future once the first has finished. + task::Poll::Ready(loop { + match self.as_mut().project() { + RuntimeApiClientFutureProj::First(fut, client) => match ready!(fut.poll(cx)) { + Ok(ok) => { + // NOTE: We use 'client.call_boxed' here to obtain a future with static + // lifetime. Otherwise, this future would need to be self-referential... + let next_fut = client + .call(ok) + .map_err(|err| { + error!(error = ?err, "failed to send request to Lambda Runtime API"); + err + }) + .boxed(); + self.set(RuntimeApiClientFuture::Second(next_fut)); + } + Err(err) => break Err(err), + }, + RuntimeApiClientFutureProj::Second(fut) => break ready!(fut.poll(cx)).map(|_| ()), + } + }) + } +} diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs new file mode 100644 index 00000000..266402cf --- /dev/null +++ b/lambda-runtime/src/layers/api_response.rs @@ -0,0 +1,173 @@ +use crate::requests::{EventCompletionRequest, IntoRequest}; +use crate::runtime::LambdaInvocation; +use crate::types::Diagnostic; +use crate::{deserializer, IntoFunctionResponse}; +use crate::{EventErrorRequest, LambdaEvent}; +use futures::ready; +use futures::Stream; +use lambda_runtime_api_client::{body::Body, BoxError}; +use pin_project::pin_project; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; +use std::task; +use tower::Service; +use tracing::{error, trace}; + +/// Tower service that turns the result or an error of a handler function into a Lambda Runtime API +/// response. +/// +/// This type is only meant for internal use in the Lambda runtime crate. The service augments both +/// inputs and outputs: the input is converted from a [LambdaInvocation] into a [LambdaEvent] +/// while any errors encountered during the conversion are turned into error responses. The service +/// outputs either a HTTP request to send to the Lambda Runtime API or a boxed error which ought to +/// be propagated to the caller to terminate the runtime. +pub struct RuntimeApiResponseService< + S, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, +> { + inner: S, + _phantom: PhantomData<( + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + )>, +} + +impl + RuntimeApiResponseService +{ + pub fn new(inner: S) -> Self { + Self { + inner, + _phantom: PhantomData, + } + } +} + +impl<'a, S, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> + Service + for RuntimeApiResponseService< + S, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + > +where + S: Service, Response = Response, Error = Diagnostic<'a>>, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + type Response = http::Request; + type Error = BoxError; + type Future = + RuntimeApiResponseFuture<'a, S::Future, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner + .poll_ready(cx) + .map_err(|err| BoxError::from(format!("{}: {}", err.error_type, err.error_message))) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + #[cfg(debug_assertions)] + if req.parts.status.is_server_error() { + error!("Lambda Runtime server returned an unexpected error"); + return RuntimeApiResponseFuture::Ready(Some(Err(req.parts.status.to_string().into()))); + } + + // Utility closure to propagate potential error from conditionally executed trace + let trace_fn = || { + trace!( + body = std::str::from_utf8(&req.body)?, + "raw JSON event received from Lambda" + ); + Ok(()) + }; + if let Err(err) = trace_fn() { + error!(error = ?err, "failed to parse raw JSON event received from Lambda"); + return RuntimeApiResponseFuture::Ready(Some(Err(err))); + }; + + let request_id = req.context.request_id.clone(); + let lambda_event = match deserializer::deserialize::(&req.body, req.context) { + Ok(lambda_event) => lambda_event, + Err(err) => match build_event_error_request(&request_id, err) { + Ok(request) => return RuntimeApiResponseFuture::Ready(Some(Ok(request))), + Err(err) => { + error!(error = ?err, "failed to build error response for Lambda Runtime API"); + return RuntimeApiResponseFuture::Ready(Some(Err(err))); + } + }, + }; + + // Once the handler input has been generated successfully, the + let fut = self.inner.call(lambda_event); + RuntimeApiResponseFuture::Future(fut, request_id, PhantomData) + } +} + +fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, BoxError> +where + T: Into> + Debug, +{ + error!(error = ?err, "building error response for Lambda Runtime API"); + EventErrorRequest::new(request_id, err).into_req() +} + +#[pin_project(project = RuntimeApiResponseFutureProj)] +pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> { + Future( + #[pin] F, + String, + PhantomData<( + &'a (), + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + )>, + ), + Ready(Option, BoxError>>), +} + +impl<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> Future + for RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +where + F: Future>>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + type Output = Result, BoxError>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + task::Poll::Ready(match self.as_mut().project() { + RuntimeApiResponseFutureProj::Future(fut, request_id, _) => match ready!(fut.poll(cx)) { + Ok(ok) => EventCompletionRequest::new(request_id, ok).into_req(), + Err(err) => EventErrorRequest::new(request_id, err).into_req(), + }, + RuntimeApiResponseFutureProj::Ready(ready) => ready.take().expect("future polled after completion"), + }) + } +} diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs new file mode 100644 index 00000000..27ce0d68 --- /dev/null +++ b/lambda-runtime/src/layers/mod.rs @@ -0,0 +1,12 @@ +// Internally used services. +mod api_client; +mod api_response; +mod panic; + +// Publicly available services. +mod trace; + +pub(crate) use api_client::RuntimeApiClientService; +pub(crate) use api_response::RuntimeApiResponseService; +pub(crate) use panic::CatchPanicService; +pub use trace::TracingLayer; diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs new file mode 100644 index 00000000..26ceeecc --- /dev/null +++ b/lambda-runtime/src/layers/panic.rs @@ -0,0 +1,118 @@ +use crate::{Diagnostic, LambdaEvent}; +use futures::{future::CatchUnwind, FutureExt}; +use pin_project::pin_project; +use std::any::Any; +use std::borrow::Cow; +use std::fmt::Debug; +use std::future::Future; +use std::marker::PhantomData; +use std::panic::AssertUnwindSafe; +use std::pin::Pin; +use std::task; +use tower::Service; +use tracing::error; + +/// Tower service that transforms panics into an error. Panics are converted to errors both when +/// constructed in [tower::Service::call] and when constructed in the returned +/// [tower::Service::Future]. +/// +/// This type is only meant for internal use in the Lambda runtime crate. It neither augments the +/// inner service's request type, nor its response type. It merely transforms the error type +/// from `Into + Debug` into `Diagnostic<'a>` to turn panics into diagnostics. +#[derive(Clone)] +pub struct CatchPanicService<'a, S> { + inner: S, + _phantom: PhantomData<&'a ()>, +} + +impl<'a, S> CatchPanicService<'a, S> { + pub fn new(inner: S) -> Self { + Self { + inner, + _phantom: PhantomData, + } + } +} + +impl<'a, S, Payload> Service> for CatchPanicService<'a, S> +where + S: Service>, + S::Future: 'a, + S::Error: Into> + Debug, +{ + type Error = Diagnostic<'a>; + type Response = S::Response; + type Future = CatchPanicFuture<'a, S::Future>; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx).map_err(|err| err.into()) + } + + fn call(&mut self, req: LambdaEvent) -> Self::Future { + // Catch panics that result from calling `call` on the service + let task = std::panic::catch_unwind(AssertUnwindSafe(|| self.inner.call(req))); + + // Catch panics that result from polling the future returned from `call` + match task { + Ok(task) => { + let fut = AssertUnwindSafe(task).catch_unwind(); + CatchPanicFuture::Future(fut, PhantomData) + } + Err(err) => { + error!(error = ?err, "user handler panicked"); + CatchPanicFuture::Error(err) + } + } + } +} + +/// Future returned by [CatchPanicService]. +#[pin_project(project = CatchPanicFutureProj)] +pub enum CatchPanicFuture<'a, F> { + Future(#[pin] CatchUnwind>, PhantomData<&'a ()>), + Error(Box), +} + +impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> +where + F: Future>, + E: Into> + Debug, +{ + type Output = Result>; + + fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { + use task::Poll; + match self.project() { + CatchPanicFutureProj::Future(fut, _) => match fut.poll(cx) { + Poll::Ready(ready) => match ready { + Ok(inner_result) => Poll::Ready(inner_result.map_err(|err| err.into())), + Err(err) => { + error!(error = ?err, "user handler panicked"); + Poll::Ready(Err(Self::build_panic_diagnostic(&err))) + } + }, + Poll::Pending => Poll::Pending, + }, + CatchPanicFutureProj::Error(err) => Poll::Ready(Err(Self::build_panic_diagnostic(err))), + } + } +} + +impl<'a, F> CatchPanicFuture<'a, F> { + fn build_panic_diagnostic(err: &Box) -> Diagnostic<'a> { + let error_type = type_name_of_val(&err); + let msg = if let Some(msg) = err.downcast_ref::<&str>() { + format!("Lambda panicked: {msg}") + } else { + "Lambda panicked".to_string() + }; + Diagnostic { + error_type: Cow::Borrowed(error_type), + error_message: Cow::Owned(msg), + } + } +} + +fn type_name_of_val(_: T) -> &'static str { + std::any::type_name::() +} diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs new file mode 100644 index 00000000..0d635154 --- /dev/null +++ b/lambda-runtime/src/layers/trace.rs @@ -0,0 +1,68 @@ +use std::env; +use tower::{Layer, Service}; +use tracing::{instrument::Instrumented, Instrument}; + +use crate::{Context, LambdaInvocation}; +use lambda_runtime_api_client::BoxError; +use std::task; + +/// Tower middleware to create a tracing span for invocations of the Lambda function. +#[derive(Default)] +pub struct TracingLayer {} + +impl TracingLayer { + /// Create a new tracing layer. + pub fn new() -> Self { + Self::default() + } +} + +impl Layer for TracingLayer { + type Service = TracingService; + + fn layer(&self, inner: S) -> Self::Service { + TracingService { inner } + } +} + +/// Tower service returned by [TracingLayer]. +pub struct TracingService { + inner: S, +} + +impl Service for TracingService +where + S: Service, +{ + type Response = (); + type Error = BoxError; + type Future = Instrumented; + + fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, req: LambdaInvocation) -> Self::Future { + let span = request_span(&req.context); + self.inner.call(req).instrument(span) + } +} + +/* ------------------------------------------- UTILS ------------------------------------------- */ + +fn request_span(ctx: &Context) -> tracing::Span { + match &ctx.xray_trace_id { + Some(trace_id) => { + env::set_var("_X_AMZN_TRACE_ID", trace_id); + tracing::info_span!( + "Lambda runtime invoke", + requestId = &ctx.request_id, + xrayTraceId = trace_id + ) + } + None => { + env::remove_var("_X_AMZN_TRACE_ID"); + tracing::info_span!("Lambda runtime invoke", requestId = &ctx.request_id) + } + } +} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 3fe56b03..9638df64 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -7,27 +7,22 @@ //! Create a type that conforms to the [`tower::Service`] trait. This type can //! then be passed to the the `lambda_runtime::run` function, which launches //! and runs the Lambda runtime. -use ::tracing::{error, trace, Instrument}; -use bytes::Bytes; -use futures::FutureExt; -use http_body_util::BodyExt; -use hyper::{body::Incoming, http::Request}; -use lambda_runtime_api_client::{body::Body, BoxError, Client}; use serde::{Deserialize, Serialize}; use std::{ - borrow::Cow, env, fmt::{self, Debug}, future::Future, - panic, sync::Arc, }; -use tokio_stream::{Stream, StreamExt}; +use tokio_stream::Stream; +use tower::util::ServiceFn; pub use tower::{self, service_fn, Service}; -use tower::{util::ServiceFn, ServiceExt}; mod deserializer; +/// Tower middleware to be applied to runtime invocatinos. +pub mod layers; mod requests; +mod runtime; /// Utilities for Lambda Streaming functions. pub mod streaming; @@ -38,13 +33,12 @@ pub use lambda_runtime_api_client::tracing; /// Types available to a Lambda function. mod types; -use requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}; +use requests::EventErrorRequest; +pub use runtime::{LambdaInvocation, Runtime}; pub use types::{ Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, }; -use types::invoke_request_id; - /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::BoxError; @@ -90,135 +84,12 @@ where service_fn(move |req: LambdaEvent| f(req.payload, req.context)) } -struct Runtime { - client: Client, - config: RefConfig, -} - -impl Runtime { - async fn run( - &self, - incoming: impl Stream, Error>> + Send, - mut handler: F, - ) -> Result<(), BoxError> - where - F: Service>, - F::Future: Future>, - F::Error: for<'a> Into> + fmt::Debug, - A: for<'de> Deserialize<'de>, - R: IntoFunctionResponse, - B: Serialize, - S: Stream> + Unpin + Send + 'static, - D: Into + Send, - E: Into + Send + Debug, - { - let client = &self.client; - tokio::pin!(incoming); - while let Some(next_event_response) = incoming.next().await { - trace!("New event arrived (run loop)"); - let event = next_event_response?; - let (parts, body) = event.into_parts(); - let request_id = invoke_request_id(&parts.headers)?; - - #[cfg(debug_assertions)] - if parts.status == http::StatusCode::NO_CONTENT { - // Ignore the event if the status code is 204. - // This is a way to keep the runtime alive when - // there are no events pending to be processed. - continue; - } - - let ctx: Context = Context::new(request_id, self.config.clone(), &parts.headers)?; - let request_span = ctx.request_span(); - - // Group the handling in one future and instrument it with the span - async { - let body = body.collect().await?.to_bytes(); - trace!( - body = std::str::from_utf8(&body)?, - "raw JSON event received from Lambda" - ); - - #[cfg(debug_assertions)] - if parts.status.is_server_error() { - error!("Lambda Runtime server returned an unexpected error"); - return Err(parts.status.to_string().into()); - } - - let lambda_event = match deserializer::deserialize(&body, ctx) { - Ok(lambda_event) => lambda_event, - Err(err) => { - let req = build_event_error_request(request_id, err)?; - client.call(req).await.expect("Unable to send response to Runtime APIs"); - return Ok(()); - } - }; - - let req = match handler.ready().await { - Ok(handler) => { - // Catches panics outside of a `Future` - let task = panic::catch_unwind(panic::AssertUnwindSafe(|| handler.call(lambda_event))); - - let task = match task { - // Catches panics inside of the `Future` - Ok(task) => panic::AssertUnwindSafe(task).catch_unwind().await, - Err(err) => Err(err), - }; - - match task { - Ok(response) => match response { - Ok(response) => { - trace!("Ok response from handler (run loop)"); - EventCompletionRequest::new(request_id, response).into_req() - } - Err(err) => build_event_error_request(request_id, err), - }, - Err(err) => { - error!("{:?}", err); - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { - format!("Lambda panicked: {msg}") - } else { - "Lambda panicked".to_string() - }; - EventErrorRequest::new( - request_id, - Diagnostic { - error_type: Cow::Borrowed(error_type), - error_message: Cow::Owned(msg), - }, - ) - .into_req() - } - } - } - Err(err) => build_event_error_request(request_id, err), - }?; - - client.call(req).await.expect("Unable to send response to Runtime APIs"); - Ok::<(), Error>(()) - } - .instrument(request_span) - .await?; - } - Ok(()) - } -} - -fn incoming(client: &Client) -> impl Stream, Error>> + Send + '_ { - async_stream::stream! { - loop { - trace!("Waiting for next event (incoming loop)"); - let req = NextEventRequest.into_req().expect("Unable to construct request"); - let res = client.call(req).await; - yield res; - } - } -} - /// Starts the Lambda Rust runtime and begins polling for events on the [Lambda /// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). /// +/// If you need more control over the runtime and add custom middleware, use the +/// [Runtime] type directly. +/// /// # Example /// ```no_run /// use lambda_runtime::{Error, service_fn, LambdaEvent}; @@ -237,272 +108,16 @@ fn incoming(client: &Client) -> impl Stream(handler: F) -> Result<(), Error> where - F: Service>, + F: Service, Response = R>, F::Future: Future>, F::Error: for<'a> Into> + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, S: Stream> + Unpin + Send + 'static, - D: Into + Send, + D: Into + Send, E: Into + Send + Debug, { - trace!("Loading config from env"); - let config = Config::from_env(); - let client = Client::builder().build().expect("Unable to create a runtime client"); - let runtime = Runtime { - client, - config: Arc::new(config), - }; - - let client = &runtime.client; - let incoming = incoming(client); - runtime.run(incoming, handler).await -} - -fn type_name_of_val(_: T) -> &'static str { - std::any::type_name::() -} - -fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, Error> -where - T: Into> + Debug, -{ - error!("{:?}", err); // logs the error in CloudWatch - EventErrorRequest::new(request_id, err).into_req() -} - -#[cfg(test)] -mod endpoint_tests { - use crate::{ - incoming, - requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, - types::Diagnostic, - Config, Error, Runtime, - }; - use futures::future::BoxFuture; - use http::{HeaderValue, StatusCode}; - use http_body_util::BodyExt; - use httpmock::prelude::*; - - use lambda_runtime_api_client::Client; - use std::{borrow::Cow, env, sync::Arc}; - use tokio_stream::StreamExt; - - #[tokio::test] - async fn test_next_event() -> Result<(), Error> { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let mock = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = NextEventRequest.into_req()?; - let rsp = client.call(req).await.expect("Unable to send request"); - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - assert_eq!( - rsp.headers()["lambda-runtime-aws-request-id"], - &HeaderValue::from_static(request_id) - ); - assert_eq!( - rsp.headers()["lambda-runtime-deadline-ms"], - &HeaderValue::from_static(deadline) - ); - - let body = rsp.into_body().collect().await?.to_bytes(); - assert_eq!("{}", std::str::from_utf8(&body)?); - Ok(()) - } - - #[tokio::test] - async fn test_ok_response() -> Result<(), Error> { - let server = MockServer::start(); - - let mock = server.mock(|when, then| { - when.method(POST) - .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") - .body("\"{}\""); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); - let req = req.into_req()?; - - let rsp = client.call(req).await?; - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - Ok(()) - } - - #[tokio::test] - async fn test_error_response() -> Result<(), Error> { - let diagnostic = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data"), - }; - let body = serde_json::to_string(&diagnostic)?; - - let server = MockServer::start(); - let mock = server.mock(|when, then| { - when.method(POST) - .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") - .header("lambda-runtime-function-error-type", "unhandled") - .body(body); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let req = EventErrorRequest { - request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", - diagnostic, - }; - let req = req.into_req()?; - let rsp = client.call(req).await?; - - mock.assert_async().await; - assert_eq!(rsp.status(), StatusCode::OK); - Ok(()) - } - - #[tokio::test] - async fn successful_end_to_end_run() -> Result<(), Error> { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let next_request = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - let next_response = server.mock(|when, then| { - when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) - .body("{}"); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - async fn func(event: crate::LambdaEvent) -> Result { - let (event, _) = event.into_parts(); - Ok(event) - } - let f = crate::service_fn(func); - - // set env vars needed to init Config if they are not already set in the environment - if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { - env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); - } - if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); - } - if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); - } - if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { - env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); - } - if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); - } - if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { - env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); - } - let config = Config::from_env(); - - let runtime = Runtime { - client, - config: Arc::new(config), - }; - let client = &runtime.client; - let incoming = incoming(client).take(1); - runtime.run(incoming, f).await?; - - next_request.assert_async().await; - next_response.assert_async().await; - Ok(()) - } - - async fn run_panicking_handler(func: F) -> Result<(), Error> - where - F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result>, - { - let server = MockServer::start(); - let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; - let deadline = "1542409706888"; - - let next_request = server.mock(|when, then| { - when.method(GET).path("/2018-06-01/runtime/invocation/next"); - then.status(200) - .header("content-type", "application/json") - .header("lambda-runtime-aws-request-id", request_id) - .header("lambda-runtime-deadline-ms", deadline) - .body("{}"); - }); - - let next_response = server.mock(|when, then| { - when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) - .header("lambda-runtime-function-error-type", "unhandled"); - then.status(200).body(""); - }); - - let base = server.base_url().parse().expect("Invalid mock server Uri"); - let client = Client::builder().with_endpoint(base).build()?; - - let f = crate::service_fn(func); - - let config = Arc::new(Config { - function_name: "test_fn".to_string(), - memory: 128, - version: "1".to_string(), - log_stream: "test_stream".to_string(), - log_group: "test_log".to_string(), - }); - - let runtime = Runtime { client, config }; - let client = &runtime.client; - let incoming = incoming(client).take(1); - runtime.run(incoming, f).await?; - - next_request.assert_async().await; - next_response.assert_async().await; - Ok(()) - } - - #[tokio::test] - async fn panic_in_async_run() -> Result<(), Error> { - run_panicking_handler(|_| Box::pin(async { panic!("This is intentionally here") })).await - } - - #[tokio::test] - async fn panic_outside_async_run() -> Result<(), Error> { - run_panicking_handler(|_| { - panic!("This is intentionally here"); - }) - .await - } + let runtime = Runtime::new(handler).layer(layers::TracingLayer::new()); + runtime.run().await } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs new file mode 100644 index 00000000..0fc328cf --- /dev/null +++ b/lambda-runtime/src/runtime.rs @@ -0,0 +1,481 @@ +use super::requests::{IntoRequest, NextEventRequest}; +use super::types::{invoke_request_id, Diagnostic, IntoFunctionResponse, LambdaEvent}; +use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; +use crate::{Config, Context}; +use http_body_util::BodyExt; +use lambda_runtime_api_client::BoxError; +use lambda_runtime_api_client::Client as ApiClient; +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use std::future::Future; +use std::sync::Arc; +use tokio_stream::{Stream, StreamExt}; +use tower::Layer; +use tower::{Service, ServiceExt}; +use tracing::trace; + +/* ----------------------------------------- INVOCATION ---------------------------------------- */ + +/// A simple container that provides information about a single invocation of a Lambda function. +pub struct LambdaInvocation { + /// The header of the request sent to invoke the Lambda function. + pub parts: http::response::Parts, + /// The body of the request sent to invoke the Lambda function. + pub body: bytes::Bytes, + /// The context of the Lambda invocation. + pub context: Context, +} + +/* ------------------------------------------ RUNTIME ------------------------------------------ */ + +/// Lambda runtime executing a handler function on incoming requests. +/// +/// Middleware can be added to a runtime using the [Runtime::layer] method in order to execute +/// logic prior to processing the incoming request and/or after the response has been sent back +/// to the Lambda Runtime API. +/// +/// # Example +/// ```no_run +/// use lambda_runtime::{Error, LambdaEvent, Runtime}; +/// use serde_json::Value; +/// use tower::service_fn; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let func = service_fn(func); +/// Runtime::new(func).run().await?; +/// Ok(()) +/// } +/// +/// async fn func(event: LambdaEvent) -> Result { +/// Ok(event.payload) +/// } +/// ```` +pub struct Runtime { + service: S, + config: Arc, + client: Arc, +} + +impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> + Runtime< + RuntimeApiClientService< + RuntimeApiResponseService< + CatchPanicService<'a, F>, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + >, + >, + > +where + F: Service, Response = Response>, + F::Future: Future>, + F::Error: Into> + Debug, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + /// Create a new runtime that executes the provided handler for incoming requests. + /// + /// In order to start the runtime and poll for events on the [Lambda Runtime + /// APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html), you must call + /// [Runtime::run]. + /// + /// Note that manually creating a [Runtime] does not add tracing to the executed handler + /// as is done by [super::run]. If you want to add the default tracing functionality, call + /// [Runtime::layer] with a [super::layers::TracingLayer]. + pub fn new(handler: F) -> Self { + trace!("Loading config from env"); + let config = Arc::new(Config::from_env()); + let client = Arc::new(ApiClient::builder().build().expect("Unable to create a runtime client")); + Self { + service: wrap_handler(handler, client.clone()), + config, + client, + } + } +} + +impl Runtime { + /// Add a new layer to this runtime. For an incoming request, this layer will be executed + /// before any layer that has been added prior. + /// + /// # Example + /// ```no_run + /// use lambda_runtime::{layers, Error, LambdaEvent, Runtime}; + /// use serde_json::Value; + /// use tower::service_fn; + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// let runtime = Runtime::new(service_fn(echo)).layer( + /// layers::TracingLayer::new() + /// ); + /// runtime.run().await?; + /// Ok(()) + /// } + /// + /// async fn echo(event: LambdaEvent) -> Result { + /// Ok(event.payload) + /// } + /// ``` + pub fn layer(self, layer: L) -> Runtime + where + L: Layer, + L::Service: Service, + { + Runtime { + client: self.client, + config: self.config, + service: layer.layer(self.service), + } + } +} + +impl Runtime +where + S: Service, +{ + /// Start the runtime and begin polling for events on the Lambda Runtime API. + pub async fn run(self) -> Result<(), BoxError> { + let incoming = incoming(&self.client); + Self::run_with_incoming(self.service, self.config, incoming).await + } + + /// Internal utility function to start the runtime with a customized incoming stream. + /// This implements the core of the [Runtime::run] method. + pub(crate) async fn run_with_incoming( + mut service: S, + config: Arc, + incoming: impl Stream, BoxError>> + Send, + ) -> Result<(), BoxError> { + tokio::pin!(incoming); + while let Some(next_event_response) = incoming.next().await { + trace!("New event arrived (run loop)"); + let event = next_event_response?; + let (parts, incoming) = event.into_parts(); + + #[cfg(debug_assertions)] + if parts.status == http::StatusCode::NO_CONTENT { + // Ignore the event if the status code is 204. + // This is a way to keep the runtime alive when + // there are no events pending to be processed. + continue; + } + + // Build the invocation such that it can be sent to the service right away + // when it is ready + let body = incoming.collect().await?.to_bytes(); + let context = Context::new(invoke_request_id(&parts.headers)?, config.clone(), &parts.headers)?; + let invocation = LambdaInvocation { parts, body, context }; + + // Wait for service to be ready + let ready = service.ready().await?; + + // Once ready, call the service which will respond to the Lambda runtime API + ready.call(invocation).await?; + } + Ok(()) + } +} + +/* ------------------------------------------- UTILS ------------------------------------------- */ + +#[allow(clippy::type_complexity)] +fn wrap_handler<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>( + handler: F, + client: Arc, +) -> RuntimeApiClientService< + RuntimeApiResponseService< + CatchPanicService<'a, F>, + EventPayload, + Response, + BufferedResponse, + StreamingResponse, + StreamItem, + StreamError, + >, +> +where + F: Service, Response = Response>, + F::Future: Future>, + F::Error: Into> + Debug, + EventPayload: for<'de> Deserialize<'de>, + Response: IntoFunctionResponse, + BufferedResponse: Serialize, + StreamingResponse: Stream> + Unpin + Send + 'static, + StreamItem: Into + Send, + StreamError: Into + Send + Debug, +{ + let safe_service = CatchPanicService::new(handler); + let response_service = RuntimeApiResponseService::new(safe_service); + RuntimeApiClientService::new(response_service, client) +} + +fn incoming( + client: &ApiClient, +) -> impl Stream, BoxError>> + Send + '_ { + async_stream::stream! { + loop { + trace!("Waiting for next event (incoming loop)"); + let req = NextEventRequest.into_req().expect("Unable to construct request"); + let res = client.call(req).await; + yield res; + } + } +} + +/* --------------------------------------------------------------------------------------------- */ +/* TESTS */ +/* --------------------------------------------------------------------------------------------- */ + +#[cfg(test)] +mod endpoint_tests { + use super::{incoming, wrap_handler}; + use crate::{ + requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, + types::Diagnostic, + Config, Error, Runtime, + }; + use futures::future::BoxFuture; + use http::{HeaderValue, StatusCode}; + use http_body_util::BodyExt; + use httpmock::prelude::*; + + use lambda_runtime_api_client::Client; + use std::{borrow::Cow, env, sync::Arc}; + use tokio_stream::StreamExt; + + #[tokio::test] + async fn test_next_event() -> Result<(), Error> { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let mock = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = NextEventRequest.into_req()?; + let rsp = client.call(req).await.expect("Unable to send request"); + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + assert_eq!( + rsp.headers()["lambda-runtime-aws-request-id"], + &HeaderValue::from_static(request_id) + ); + assert_eq!( + rsp.headers()["lambda-runtime-deadline-ms"], + &HeaderValue::from_static(deadline) + ); + + let body = rsp.into_body().collect().await?.to_bytes(); + assert_eq!("{}", std::str::from_utf8(&body)?); + Ok(()) + } + + #[tokio::test] + async fn test_ok_response() -> Result<(), Error> { + let server = MockServer::start(); + + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/response") + .body("\"{}\""); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = EventCompletionRequest::new("156cb537-e2d4-11e8-9b34-d36013741fb9", "{}"); + let req = req.into_req()?; + + let rsp = client.call(req).await?; + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) + } + + #[tokio::test] + async fn test_error_response() -> Result<(), Error> { + let diagnostic = Diagnostic { + error_type: Cow::Borrowed("InvalidEventDataError"), + error_message: Cow::Borrowed("Error parsing event data"), + }; + let body = serde_json::to_string(&diagnostic)?; + + let server = MockServer::start(); + let mock = server.mock(|when, then| { + when.method(POST) + .path("/2018-06-01/runtime/invocation/156cb537-e2d4-11e8-9b34-d36013741fb9/error") + .header("lambda-runtime-function-error-type", "unhandled") + .body(body); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let req = EventErrorRequest { + request_id: "156cb537-e2d4-11e8-9b34-d36013741fb9", + diagnostic, + }; + let req = req.into_req()?; + let rsp = client.call(req).await?; + + mock.assert_async().await; + assert_eq!(rsp.status(), StatusCode::OK); + Ok(()) + } + + #[tokio::test] + async fn successful_end_to_end_run() -> Result<(), Error> { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .body("{}"); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + async fn func(event: crate::LambdaEvent) -> Result { + let (event, _) = event.into_parts(); + Ok(event) + } + let f = crate::service_fn(func); + + // set env vars needed to init Config if they are not already set in the environment + if env::var("AWS_LAMBDA_RUNTIME_API").is_err() { + env::set_var("AWS_LAMBDA_RUNTIME_API", server.base_url()); + } + if env::var("AWS_LAMBDA_FUNCTION_NAME").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_NAME", "test_fn"); + } + if env::var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_MEMORY_SIZE", "128"); + } + if env::var("AWS_LAMBDA_FUNCTION_VERSION").is_err() { + env::set_var("AWS_LAMBDA_FUNCTION_VERSION", "1"); + } + if env::var("AWS_LAMBDA_LOG_STREAM_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_STREAM_NAME", "test_stream"); + } + if env::var("AWS_LAMBDA_LOG_GROUP_NAME").is_err() { + env::set_var("AWS_LAMBDA_LOG_GROUP_NAME", "test_log"); + } + let config = Config::from_env(); + + let client = Arc::new(client); + let runtime = Runtime { + client: client.clone(), + config: Arc::new(config), + service: wrap_handler(f, client), + }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + Runtime::run_with_incoming(runtime.service, runtime.config, incoming).await?; + + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) + } + + async fn run_panicking_handler(func: F) -> Result<(), Error> + where + F: FnMut(crate::LambdaEvent) -> BoxFuture<'static, Result> + + Send + + 'static, + { + let server = MockServer::start(); + let request_id = "156cb537-e2d4-11e8-9b34-d36013741fb9"; + let deadline = "1542409706888"; + + let next_request = server.mock(|when, then| { + when.method(GET).path("/2018-06-01/runtime/invocation/next"); + then.status(200) + .header("content-type", "application/json") + .header("lambda-runtime-aws-request-id", request_id) + .header("lambda-runtime-deadline-ms", deadline) + .body("{}"); + }); + + let next_response = server.mock(|when, then| { + when.method(POST) + .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .header("lambda-runtime-function-error-type", "unhandled"); + then.status(200).body(""); + }); + + let base = server.base_url().parse().expect("Invalid mock server Uri"); + let client = Client::builder().with_endpoint(base).build()?; + + let f = crate::service_fn(func); + + let config = Arc::new(Config { + function_name: "test_fn".to_string(), + memory: 128, + version: "1".to_string(), + log_stream: "test_stream".to_string(), + log_group: "test_log".to_string(), + }); + + let client = Arc::new(client); + let runtime = Runtime { + client: client.clone(), + config, + service: wrap_handler(f, client), + }; + let client = &runtime.client; + let incoming = incoming(client).take(1); + Runtime::run_with_incoming(runtime.service, runtime.config, incoming).await?; + + next_request.assert_async().await; + next_response.assert_async().await; + Ok(()) + } + + #[tokio::test] + async fn panic_in_async_run() -> Result<(), Error> { + run_panicking_handler(|_| Box::pin(async { panic!("This is intentionally here") })).await + } + + #[tokio::test] + async fn panic_outside_async_run() -> Result<(), Error> { + run_panicking_handler(|_| { + panic!("This is intentionally here"); + }) + .await + } +} diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index 478f88fd..b4f10f71 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -7,12 +7,10 @@ use serde::{Deserialize, Serialize}; use std::{ borrow::Cow, collections::HashMap, - env, fmt::{Debug, Display}, time::{Duration, SystemTime}, }; use tokio_stream::Stream; -use tracing::Span; /// Diagnostic information about an error. /// @@ -209,24 +207,6 @@ impl Context { pub fn deadline(&self) -> SystemTime { SystemTime::UNIX_EPOCH + Duration::from_millis(self.deadline) } - - /// Create a new [`tracing::Span`] for an incoming invocation. - pub(crate) fn request_span(&self) -> Span { - match &self.xray_trace_id { - Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); - tracing::info_span!( - "Lambda runtime invoke", - requestId = &self.request_id, - xrayTraceId = trace_id - ) - } - None => { - env::remove_var("_X_AMZN_TRACE_ID"); - tracing::info_span!("Lambda runtime invoke", requestId = &self.request_id) - } - } - } } /// Extract the invocation request id from the incoming request. From ddd22a3cf9d0d1e8428abd2549253871dea81495 Mon Sep 17 00:00:00 2001 From: Michael Wallace Date: Mon, 25 Mar 2024 19:26:07 -0700 Subject: [PATCH 105/211] Add events for CDK custom resource provider framework. (#846) Co-authored-by: Michael Wallace --- lambda-events/src/event/cloudformation/mod.rs | 2 + .../src/event/cloudformation/provider.rs | 159 ++++++++++++++++++ ...stom-resource-provider-create-request.json | 12 ++ ...stom-resource-provider-delete-request.json | 13 ++ ...ion-custom-resource-provider-response.json | 9 + ...stom-resource-provider-update-request.json | 18 ++ 6 files changed, 213 insertions(+) create mode 100644 lambda-events/src/event/cloudformation/provider.rs create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json create mode 100644 lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 3123d8af..8dbf8b5e 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -2,6 +2,8 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +pub mod provider; + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs new file mode 100644 index 00000000..e3ba5bea --- /dev/null +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -0,0 +1,159 @@ +//! These events are to be used with the CDK custom resource provider framework. +//! +//! Note that they are similar (but not the same) as the events in the `super` module. +//! +//! See https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html for details. + +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(tag = "RequestType")] +pub enum CloudFormationCustomResourceRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + #[serde(bound = "")] + Create(CreateRequest), + #[serde(bound = "")] + Update(UpdateRequest), + #[serde(bound = "")] + Delete(DeleteRequest), +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CreateRequest +where + P2: DeserializeOwned + Serialize, +{ + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct UpdateRequest +where + P1: DeserializeOwned + Serialize, + P2: DeserializeOwned + Serialize, +{ + pub physical_resource_id: String, + + #[serde(bound = "")] + pub old_resource_properties: P1, + + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct DeleteRequest +where + P2: DeserializeOwned + Serialize, +{ + pub physical_resource_id: String, + + #[serde(flatten, bound = "")] + pub common: CommonRequestParams, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct CommonRequestParams +where + P2: DeserializeOwned + Serialize, +{ + pub logical_resource_id: String, + #[serde(bound = "")] + pub resource_properties: P2, + pub resource_type: String, + pub request_id: String, + pub stack_id: String, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[serde(rename_all = "PascalCase")] +pub struct CloudFormationCustomResourceResponse +where + D: DeserializeOwned + Serialize, +{ + pub physical_resource_id: Option, + #[serde(bound = "")] + pub data: D, + pub no_echo: bool, +} + +#[cfg(test)] +mod test { + use std::collections::HashMap; + + use super::CloudFormationCustomResourceRequest::*; + use super::*; + + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] + #[serde(rename_all = "PascalCase")] + struct TestProperties { + key_1: String, + key_2: Vec, + key_3: HashMap, + } + + type TestRequest = CloudFormationCustomResourceRequest; + + #[test] + fn example_create_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-create-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Create(_) => (), + _ => panic!("expected Create request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_update_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-update-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Update(_) => (), + _ => panic!("expected Update request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_delete_request() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-delete-request.json"); + let parsed: TestRequest = serde_json::from_slice(data).unwrap(); + + match parsed { + Delete(_) => (), + _ => panic!("expected Delete request"), + } + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: TestRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + fn example_response() { + let data = include_bytes!("../../fixtures/example-cloudformation-custom-resource-provider-response.json"); + let parsed: CloudFormationCustomResourceResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CloudFormationCustomResourceResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json new file mode 100644 index 00000000..074a3712 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-create-request.json @@ -0,0 +1,12 @@ +{ + "RequestType" : "Create", + "RequestId" : "82304eb2-bdda-469f-a33b-a3f1406d0a52", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json new file mode 100644 index 00000000..3fb58391 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-delete-request.json @@ -0,0 +1,13 @@ +{ + "RequestType" : "Delete", + "RequestId" : "ef70561d-d4ba-42a4-801b-33ad88dafc37", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json new file mode 100644 index 00000000..cff06191 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-response.json @@ -0,0 +1,9 @@ +{ + "PhysicalResourceId": "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "NoEcho": false, + "Data": { + "Key1": "a", + "Key2": "b", + "Key3": "c" + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json new file mode 100644 index 00000000..90280f72 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudformation-custom-resource-provider-update-request.json @@ -0,0 +1,18 @@ +{ + "RequestType" : "Update", + "RequestId" : "49347ca5-c603-44e5-a34b-10cf1854a887", + "StackId" : "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-name/16580499-7622-4a9c-b32f-4eba35da93da", + "ResourceType" : "Custom::MyCustomResourceType", + "LogicalResourceId" : "CustomResource", + "PhysicalResourceId" : "custom-resource-f4bd5382-3de3-4caf-b7ad-1be06b899647", + "ResourceProperties" : { + "Key1" : "new-string", + "Key2" : [ "new-list" ], + "Key3" : { "Key4" : "new-map" } + }, + "OldResourceProperties" : { + "Key1" : "string", + "Key2" : [ "list" ], + "Key3" : { "Key4" : "map" } + } +} From e0a3827ec1c425364fa10be2122d15f61f436d5f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 27 Mar 2024 18:40:44 -0700 Subject: [PATCH 106/211] Release version 0.11.0 (#847) - Release the new layering system for the runtime. Signed-off-by: David Calavera --- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index fba19357..706dd4db 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index e05347ec..ef89aaee 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.10.0" +version = "0.11.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.10.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -53,7 +53,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 819d5ed7..e5c920bd 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index d9eca35a..6386ae16 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.10.0" +version = "0.11.0" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.10", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From 165abcf3ae39e1cbb9ca3df72dc8f0dd1819cec7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 29 Mar 2024 18:51:21 -0700 Subject: [PATCH 107/211] Always export the _X_AMZN_TRACE_ID env variable. (#850) This variable is expected across runtimes regardless of other features. Signed-off-by: David Calavera --- lambda-runtime/src/layers/trace.rs | 3 --- lambda-runtime/src/runtime.rs | 11 +++++++++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 0d635154..7a9f8370 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -1,4 +1,3 @@ -use std::env; use tower::{Layer, Service}; use tracing::{instrument::Instrumented, Instrument}; @@ -53,7 +52,6 @@ where fn request_span(ctx: &Context) -> tracing::Span { match &ctx.xray_trace_id { Some(trace_id) => { - env::set_var("_X_AMZN_TRACE_ID", trace_id); tracing::info_span!( "Lambda runtime invoke", requestId = &ctx.request_id, @@ -61,7 +59,6 @@ fn request_span(ctx: &Context) -> tracing::Span { ) } None => { - env::remove_var("_X_AMZN_TRACE_ID"); tracing::info_span!("Lambda runtime invoke", requestId = &ctx.request_id) } } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 0fc328cf..5ded610c 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -6,6 +6,7 @@ use http_body_util::BodyExt; use lambda_runtime_api_client::BoxError; use lambda_runtime_api_client::Client as ApiClient; use serde::{Deserialize, Serialize}; +use std::env; use std::fmt::Debug; use std::future::Future; use std::sync::Arc; @@ -176,6 +177,9 @@ where let context = Context::new(invoke_request_id(&parts.headers)?, config.clone(), &parts.headers)?; let invocation = LambdaInvocation { parts, body, context }; + // Setup Amazon's default tracing data + amzn_trace_env(&invocation.context); + // Wait for service to be ready let ready = service.ready().await?; @@ -232,6 +236,13 @@ fn incoming( } } +fn amzn_trace_env(ctx: &Context) { + match &ctx.xray_trace_id { + Some(trace_id) => env::set_var("_X_AMZN_TRACE_ID", trace_id), + None => env::remove_var("_X_AMZN_TRACE_ID"), + } +} + /* --------------------------------------------------------------------------------------------- */ /* TESTS */ /* --------------------------------------------------------------------------------------------- */ From c7a301484f49c4a2347a07424da67ca58de2d4a8 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 29 Mar 2024 19:12:02 -0700 Subject: [PATCH 108/211] Release runtime 0.11.1 (#851) Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index ef89aaee..f8fe06c3 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.0" +version = "0.11.1" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.1", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6386ae16..8745b59f 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.0" +version = "0.11.1" authors = [ "David Calavera ", "Harold Sun ", From 5b1de1dc9331cb91907362598e67bcc3b00fadc4 Mon Sep 17 00:00:00 2001 From: Janosch Reppnow <40561502+jreppnow@users.noreply.github.com> Date: Tue, 2 Apr 2024 00:54:31 +0900 Subject: [PATCH 109/211] fix: do not duplicate headers (single val, multi val) on return (#852) * fix: do not duplicate headers (single val, multi val) on return * fix: add explainer comment * fix: rustfmt --- lambda-http/src/response.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index cc721d46..e73a584d 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -69,7 +69,9 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }), #[cfg(feature = "apigw_http")] @@ -91,7 +93,9 @@ impl LambdaResponse { is_base64_encoded, status_code: status_code as i64, cookies, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }) } @@ -100,7 +104,9 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, status_description: Some(format!( "{} {}", @@ -113,7 +119,9 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - headers: headers.clone(), + // explicitly empty, as API gateway does not properly merge headers and + // multi-value-headers, resulting in duplicate headers + headers: HeaderMap::new(), multi_value_headers: headers, }), #[cfg(feature = "pass_through")] @@ -465,7 +473,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-encoding":"gzip"},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# ) } @@ -483,7 +491,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# ) } @@ -501,7 +509,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -519,7 +527,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -553,7 +561,7 @@ mod tests { let json = serde_json::to_string(&res).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{"multi":"a"},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# + r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"multi":["a","b"]},"isBase64Encoded":false}"# ) } From 4a3dbc99c5c6802c363f214472f864fc5c721f0d Mon Sep 17 00:00:00 2001 From: Adam Esterline Date: Tue, 2 Apr 2024 19:33:40 -0600 Subject: [PATCH 110/211] 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}" +} From 115822a230acf9521a722b5877707ba39492c94c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 2 Apr 2024 20:30:22 -0700 Subject: [PATCH 111/211] Pin webp version (#855) The latest patch version updates a dependency that breaks our examples. Signed-off-by: David Calavera --- examples/basic-s3-object-lambda-thumbnail/Cargo.toml | 3 ++- examples/basic-s3-thumbnail/Cargo.toml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml index ba750df9..905362c9 100644 --- a/examples/basic-s3-object-lambda-thumbnail/Cargo.toml +++ b/examples/basic-s3-object-lambda-thumbnail/Cargo.toml @@ -21,11 +21,12 @@ serde = "1" tokio = { version = "1", features = ["macros"] } aws-config = "0.55.3" aws-sdk-s3 = "0.28.0" -thumbnailer = "0.4.0" +thumbnailer = "0.5.1" mime = "0.3.16" async-trait = "0.1.66" ureq = "2.6.2" aws-smithy-http = "0.55.3" +webp = "=0.2.1" [dev-dependencies] mockall = "0.11.3" diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 1cd3b834..4b9ef3da 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -22,9 +22,10 @@ tokio = { version = "1", features = ["macros"] } aws-config = "0.55" aws-smithy-http = "0.55.3" aws-sdk-s3 = "0.28" -thumbnailer = "0.4.0" +thumbnailer = "0.5.1" mime = "0.3.16" async-trait = "0.1.68" +webp = "=0.2.1" [dev-dependencies] mockall = "0.11" From bb5a25ce665198a28f0fe44f18e4b54142df83c2 Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Thu, 4 Apr 2024 05:55:12 +0300 Subject: [PATCH 112/211] Allow disabling `tracing` feature (#857) Without this PR, the `tracing` in the `lambda_runtime_api_client` is not really optional, when that crate is added to the dep tree by the main `lambda_runtime` crate. --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 8745b59f..c4b8e35b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client", default-features = false } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From a68de584154958c524692cb43dc208d520d05a13 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 4 Apr 2024 18:04:19 -0700 Subject: [PATCH 113/211] Cleanup IAM definitions (#856) * Cleanup IAM definitions We have two different definitions of IAM policies and statement. These changes centralize them both into one. These changes also add the `Condition` field that was missing from both implementations. These changes also make `Effect` to be an enum with the default `Allow`, instead of an optional string. This is more ergonomic than checking whether the effect is none or `allow`. Signed-off-by: David Calavera * Remove unnecesary module. Keep all deserialization logic together, it's easier to manage. Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-events/src/custom_serde/mod.rs | 21 --- lambda-events/src/event/apigw/mod.rs | 42 +++-- lambda-events/src/event/iam/mod.rs | 171 ++++++++++++++++-- ...w-custom-auth-response-with-condition.json | 30 +++ .../example-apigw-custom-auth-response.json | 40 ++-- 6 files changed, 234 insertions(+), 72 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index eb5d9c31..8f2fca99 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -85,7 +85,7 @@ default = [ activemq = [] alb = ["bytes", "http", "http-body", "http-serde", "query_map"] -apigw = ["bytes", "http", "http-body", "http-serde", "query_map"] +apigw = ["bytes", "http", "http-body", "http-serde", "iam", "query_map"] appsync = [] autoscaling = ["chrono"] bedrock_agent_runtime = [] diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 9d68c8d3..030cb5b3 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -92,27 +92,6 @@ 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 1777cf76..533bb77e 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,8 +1,9 @@ use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, deserialize_string_or_slice, http_method, - serialize_headers, serialize_multi_value_headers, + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, }; use crate::encodings::Body; +use crate::iam::IamPolicyStatement; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; @@ -723,30 +724,13 @@ where /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "PascalCase")] pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(default)] - #[serde(rename = "Version")] pub version: Option, - #[serde(rename = "Statement")] pub statement: Vec, } -/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -#[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, -} - fn default_http_method() -> Method { Method::GET } @@ -1045,4 +1029,22 @@ mod test { assert_eq!(Some(1), fields.get("clientId").unwrap().as_u64()); assert_eq!(Some("Exata"), fields.get("clientName").unwrap().as_str()); } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response_with_statement_condition() { + use crate::iam::IamPolicyEffect; + + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response-with-condition.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); + + let statement = parsed.policy_document.statement.first().unwrap(); + assert_eq!(IamPolicyEffect::Deny, statement.effect); + + let condition = statement.condition.as_ref().unwrap(); + assert_eq!(vec!["xxx"], condition["StringEquals"]["aws:SourceIp"]); + } } diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 12bf7ba9..ee35bbc8 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,25 +1,172 @@ -use serde::{Deserialize, Serialize}; +use std::{borrow::Cow, collections::HashMap, fmt}; + +use serde::{ + de::{Error as DeError, MapAccess, Visitor}, + Deserialize, Deserializer, Serialize, +}; /// `IamPolicyDocument` represents an IAM policy document. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] +#[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { #[serde(default)] - #[serde(rename = "Version")] pub version: Option, - #[serde(rename = "Statement")] pub statement: Vec, } -/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] +/// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] 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(default = "default_statement_effect")] + pub effect: IamPolicyEffect, + #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, + #[serde(default, deserialize_with = "deserialize_policy_condition")] + pub condition: Option, +} + +pub type IamPolicyCondition = HashMap>>; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub enum IamPolicyEffect { + #[default] + Allow, + Deny, +} + +fn default_statement_effect() -> IamPolicyEffect { + IamPolicyEffect::Allow +} + +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum StringOrSlice { + String(String), + Slice(Vec), +} + +/// Deserializes `Vec`, from a JSON `string` or `[string]`. +fn deserialize_string_or_slice<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let string_or_slice = StringOrSlice::deserialize(deserializer)?; + + match string_or_slice { + StringOrSlice::Slice(slice) => Ok(slice), + StringOrSlice::String(s) => Ok(vec![s]), + } +} + +fn deserialize_policy_condition<'de, D>(de: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + de.deserialize_option(IamPolicyConditionVisitor) +} + +struct IamPolicyConditionVisitor; + +impl<'de> Visitor<'de> for IamPolicyConditionVisitor { + type Value = Option; + + // Format a message stating what data this Visitor expects to receive. + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("lots of things can go wrong with a IAM Policy Condition") + } + + fn visit_unit(self) -> Result + where + E: DeError, + { + Ok(None) + } + + fn visit_none(self) -> Result + where + E: DeError, + { + Ok(None) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(self) + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut map = HashMap::with_capacity(access.size_hint().unwrap_or(0)); + + while let Some((key, val)) = access.next_entry::, HashMap, StringOrSlice>>()? { + let mut value = HashMap::with_capacity(val.len()); + for (val_key, string_or_slice) in val { + let val = match string_or_slice { + StringOrSlice::Slice(slice) => slice, + StringOrSlice::String(s) => vec![s], + }; + value.insert(val_key.into_owned(), val); + } + + map.insert(key.into_owned(), value); + } + + Ok(Some(map)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_string_condition() { + let data = serde_json::json!({ + "condition": { + "StringEquals": { + "iam:RegisterSecurityKey": "Activate", + "iam:FIDO-certification": "L1plus" + } + } + }); + + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_policy_condition")] + condition: Option, + } + + let test: Test = serde_json::from_value(data).unwrap(); + let condition = test.condition.unwrap(); + assert_eq!(1, condition.len()); + + assert_eq!(vec!["Activate"], condition["StringEquals"]["iam:RegisterSecurityKey"]); + assert_eq!(vec!["L1plus"], condition["StringEquals"]["iam:FIDO-certification"]); + } + + #[test] + fn test_deserialize_slide_condition() { + let data = serde_json::json!({ + "condition": {"StringLike": {"s3:prefix": ["janedoe/*"]}} + }); + + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_policy_condition")] + condition: Option, + } + + let test: Test = serde_json::from_value(data).unwrap(); + let condition = test.condition.unwrap(); + assert_eq!(1, condition.len()); + + assert_eq!(vec!["janedoe/*"], condition["StringLike"]["s3:prefix"]); + } } diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json new file mode 100644 index 00000000..53a09b39 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response-with-condition.json @@ -0,0 +1,30 @@ +{ + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "execute-api:Invoke" + ], + "Effect": "Deny", + "Resource": [ + "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + ], + "Condition": { + "StringEquals": { + "aws:SourceIp": [ + "xxx" + ] + } + } + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json index 9b624141..b1502cde 100644 --- a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json @@ -1,19 +1,23 @@ { - "principalId": "yyyyyyyy", - "policyDocument": { - "Version": "2012-10-17", - "Statement": [ - { - "Action": ["execute-api:Invoke"], - "Effect": "Allow|Deny", - "Resource": ["arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]"] - } - ] - }, - "context": { - "stringKey": "value", - "numberKey": "1", - "booleanKey": "true" - }, - "usageIdentifierKey": "{api-key}" -} + "principalId": "yyyyyyyy", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "execute-api:Invoke" + ], + "Effect": "Deny", + "Resource": [ + "arn:aws:execute-api:{regionId}:{accountId}:{appId}/{stage}/{httpVerb}/[{resource}/[child-resources]]" + ] + } + ] + }, + "context": { + "stringKey": "value", + "numberKey": "1", + "booleanKey": "true" + }, + "usageIdentifierKey": "{api-key}" +} \ No newline at end of file From 46ba8c7a3747d554fd27f785629ac0c0de550669 Mon Sep 17 00:00:00 2001 From: Luciano Mammino Date: Fri, 12 Apr 2024 08:45:33 +0100 Subject: [PATCH 114/211] Update tracing.rs - AWS_LAMBDA_LOG_LEVEL rather than RUST_LOG (#859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update tracing.rs - AWS_LAMBDA_LOG_LEVEL rather than RUST_LOG * Update tracing.rs LEVEL 😅 --- lambda-runtime-api-client/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index babc817c..6a15f36c 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -16,7 +16,7 @@ pub use tracing::*; pub use tracing_subscriber as subscriber; /// Initialize `tracing-subscriber` with default options. -/// The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. +/// The subscriber uses `AWS_LAMBDA_LOG_LEVEL` as the environment variable to determine the log level for your function. /// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. /// By default, the log level to emit events is `INFO`. From aed829506623ef0887be6fa845f5b1f166d2a803 Mon Sep 17 00:00:00 2001 From: HVKukkonen <54474620+HVKukkonen@users.noreply.github.com> Date: Mon, 22 Apr 2024 04:19:49 +0300 Subject: [PATCH 115/211] ApiGatewayV2CustomAuthorizerV2Request identity_source as optional (#860) --- lambda-events/src/event/apigw/mod.rs | 14 +++++- ...er-v2-request-without-identity-source.json | 50 +++++++++++++++++++ 2 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 533bb77e..d8310755 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -565,7 +565,8 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { /// nolint: stylecheck #[serde(default)] pub route_arn: Option, - pub identity_source: Vec, + #[serde(skip_serializing_if = "Option::is_none")] + pub identity_source: Option>, #[serde(default)] pub route_key: Option, #[serde(default)] @@ -1008,6 +1009,17 @@ mod test { assert_eq!(parsed, reparsed); } + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v2_request_without_identity_source() { + let data = + include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json"); + let parsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayV2CustomAuthorizerV2Request = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_console_request() { diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json new file mode 100644 index 00000000..cc79dda3 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-identity-source.json @@ -0,0 +1,50 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "cookies": ["cookie1", "cookie2"], + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "api-id", + "authentication": { + "clientCert": { + "clientCertPem": "CERT_CONTENT", + "subjectDN": "www.example.com", + "issuerDN": "Example issuer", + "serialNumber": "a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1:a1", + "validity": { + "notBefore": "May 28 12:30:02 2019 GMT", + "notAfter": "Aug 5 09:36:04 2021 GMT" + } + } + }, + "domainName": "id.execute-api.us-east-1.amazonaws.com", + "domainPrefix": "id", + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "requestId": "id", + "routeKey": "$default", + "stage": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + }, + "pathParameters": { "parameter1": "value1" }, + "stageVariables": { "stageVariable1": "value1", "stageVariable2": "value2" } +} + From de822f9d870c21c06b504d218293099f691ced9f Mon Sep 17 00:00:00 2001 From: Christian Hoffmann <352753+hffmnn@users.noreply.github.com> Date: Tue, 23 Apr 2024 03:51:55 +0200 Subject: [PATCH 116/211] fix(IamPolicyStatement): don't serialize None condition (#864) --- lambda-events/src/event/iam/mod.rs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index ee35bbc8..36f59c7b 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -25,6 +25,7 @@ pub struct IamPolicyStatement { #[serde(deserialize_with = "deserialize_string_or_slice")] pub resource: Vec, #[serde(default, deserialize_with = "deserialize_policy_condition")] + #[serde(skip_serializing_if = "Option::is_none")] pub condition: Option, } @@ -169,4 +170,24 @@ mod tests { assert_eq!(vec!["janedoe/*"], condition["StringLike"]["s3:prefix"]); } + + #[test] + fn test_serialize_none_condition() { + let policy = IamPolicyStatement { + action: vec!["some:action".into()], + effect: IamPolicyEffect::Allow, + resource: vec!["some:resource".into()], + condition: None, + }; + let policy_ser = serde_json::to_value(policy).unwrap(); + + assert_eq!( + policy_ser, + serde_json::json!({ + "Action": ["some:action"], + "Effect": "Allow", + "Resource": ["some:resource"] + }) + ); + } } From 41f5fb5bcf0da5af10eb078b38075698343ac016 Mon Sep 17 00:00:00 2001 From: Jakub Wieczorek Date: Mon, 6 May 2024 01:46:02 +0200 Subject: [PATCH 117/211] Upgrade base64 to 0.22. (#867) --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 09b046e8..0b01ee05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ members = [ exclude = ["examples"] [workspace.dependencies] -base64 = "0.21" +base64 = "0.22" bytes = "1" chrono = "0.4.35" futures = "0.3" From 94f7e100fde9008a0cc5b7a4f60ff7d59624bde7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 5 May 2024 20:58:44 -0700 Subject: [PATCH 118/211] Remove unused code. (#868) Cleanup an unused trait. Move tests into a tests module. Signed-off-by: David Calavera --- lambda-runtime/src/requests.rs | 107 +++++++++++++++------------------ 1 file changed, 48 insertions(+), 59 deletions(-) diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index d1e25e32..ec893710 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -2,7 +2,7 @@ use crate::types::ToStreamErrorTrailer; use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; -use http::{Method, Request, Response, Uri}; +use http::{Method, Request, Uri}; use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; use std::fmt::Debug; @@ -14,10 +14,6 @@ pub(crate) trait IntoRequest { fn into_req(self) -> Result, Error>; } -pub(crate) trait IntoResponse { - fn into_rsp(self) -> Result, Error>; -} - // /runtime/invocation/next #[derive(Debug, Eq, PartialEq)] pub(crate) struct NextEventRequest; @@ -46,30 +42,6 @@ pub struct NextEventResponse<'a> { pub body: Vec, } -impl<'a> IntoResponse for NextEventResponse<'a> { - fn into_rsp(self) -> Result, Error> { - // let body: BoxyBody< = BoxBody::new(); - let rsp = Response::builder() - .header("lambda-runtime-aws-request-id", self.request_id) - .header("lambda-runtime-deadline-ms", self.deadline) - .header("lambda-runtime-invoked-function-arn", self.arn) - .header("lambda-runtime-trace-id", self.trace_id) - .body(Body::from(self.body))?; - Ok(rsp) - } -} -#[test] -fn test_next_event_request() { - let req = NextEventRequest; - let req = req.into_req().unwrap(); - assert_eq!(req.method(), Method::GET); - assert_eq!(req.uri(), &Uri::from_static("/2018-06-01/runtime/invocation/next")); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); -} - // /runtime/invocation/{AwsRequestId}/response pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> where @@ -218,25 +190,6 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { } } -#[test] -fn test_event_error_request() { - let req = EventErrorRequest { - request_id: "id", - diagnostic: Diagnostic { - error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), - error_message: std::borrow::Cow::Borrowed("Error parsing event data"), - }, - }; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); -} - // /runtime/init/error struct InitErrorRequest; @@ -254,15 +207,51 @@ impl IntoRequest for InitErrorRequest { } } -#[test] -fn test_init_error_request() { - let req = InitErrorRequest; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/init/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_next_event_request() { + let req = NextEventRequest; + let req = req.into_req().unwrap(); + assert_eq!(req.method(), Method::GET); + assert_eq!(req.uri(), &Uri::from_static("/2018-06-01/runtime/invocation/next")); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } + + #[test] + fn test_event_error_request() { + let req = EventErrorRequest { + request_id: "id", + diagnostic: Diagnostic { + error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), + error_message: std::borrow::Cow::Borrowed("Error parsing event data"), + }, + }; + let req = req.into_req().unwrap(); + let expected = Uri::from_static("/2018-06-01/runtime/invocation/id/error"); + assert_eq!(req.method(), Method::POST); + assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } + + #[test] + fn test_init_error_request() { + let req = InitErrorRequest; + let req = req.into_req().unwrap(); + let expected = Uri::from_static("/2018-06-01/runtime/init/error"); + assert_eq!(req.method(), Method::POST); + assert_eq!(req.uri(), &expected); + assert!(match req.headers().get("User-Agent") { + Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), + None => false, + }); + } } From cfdf6f5741ed6113a5591f3075f54637ee1d2f81 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 May 2024 18:58:57 -0700 Subject: [PATCH 119/211] Support RUST_LOG as variable to set the logging level. (#869) Improve the documentation about log levels and formats. Signed-off-by: David Calavera --- lambda-runtime-api-client/src/tracing.rs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 6a15f36c..51e187e4 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -15,14 +15,25 @@ pub use tracing::*; /// Re-export the `tracing-subscriber` crate to build your own subscribers. pub use tracing_subscriber as subscriber; -/// Initialize `tracing-subscriber` with default options. -/// The subscriber uses `AWS_LAMBDA_LOG_LEVEL` as the environment variable to determine the log level for your function. -/// It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +const DEFAULT_LOG_LEVEL: &str = "INFO"; + +/// Initialize `tracing-subscriber` with default logging options. +/// +/// This function uses environment variables set with [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. -/// By default, the log level to emit events is `INFO`. +/// +/// This subscriber sets the logging level based on environment variables: +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes predecence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. +/// - if none of those two variables are set, use `INFO` as the logging level. +/// +/// The logging format can also be changed based on Lambda's advanced logging controls. +/// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, +/// otherwise they will be formatted with the default tracing format. pub fn init_default_subscriber() { let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); - let log_level = Level::from_str(&env::var("AWS_LAMBDA_LOG_LEVEL").unwrap_or_default()).unwrap_or(Level::INFO); + let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); + let log_level = Level::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(Level::INFO); let collector = tracing_subscriber::fmt() .with_target(false) From f9aeac7412ad15c57c29d53aa18a4cef9361daf1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 7 May 2024 21:15:41 -0700 Subject: [PATCH 120/211] Release patch versions. (#870) Signed-off-by: David Calavera --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 8f2fca99..6d496b43 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.15.0" +version = "0.15.1" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index f8fe06c3..8f1b1d26 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.1" +version = "0.11.2" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.1", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -53,7 +53,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index e5c920bd..57fc4bca 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.11.0" +version = "0.11.1" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index c4b8e35b..1883a18d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.1" +version = "0.11.2" authors = [ "David Calavera ", "Harold Sun ", @@ -36,7 +36,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client", default-features = false } +lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From fc49dd5e0f1dd0c81bdb51c6bfcfb755583ba7ff Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 May 2024 18:35:10 -0700 Subject: [PATCH 121/211] Fix Lambda Function URL responses. (#872) Add the headers field to ensure that Lambda Function URL responses are encoded correctly. Signed-off-by: David Calavera --- lambda-http/src/response.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e73a584d..c1b48ef2 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -104,9 +104,11 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers - headers: HeaderMap::new(), + // ALB responses are used for ALB integrations as well as + // Lambda Function URLs. The former uses the `multi_value_headers` field, + // while the later uses the `headers` field. We need to return + // both fields to ensure both integrations work correctly. + headers: headers.clone(), multi_value_headers: headers, status_description: Some(format!( "{} {}", From 343159e92c09fb84d47f4b2a2a106ce1d6059efd Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 May 2024 09:20:06 -0700 Subject: [PATCH 122/211] Release version 0.11.3 (#875) Fixes APIGW duplicated response headers. Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 8f1b1d26..9bd5923e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.2" +version = "0.11.3" authors = [ "David Calavera ", "Harold Sun ", From 447d9fb0c877100109536d82c9925f5a23b1b741 Mon Sep 17 00:00:00 2001 From: fluxth Date: Fri, 17 May 2024 10:11:17 +0900 Subject: [PATCH 123/211] fix: return correct header keys for each integration (#877) Each integration handle header keys differently, this patch tries to address these differences so that we have proper headers in responses. **ALB Integration** https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html#multi-value-headers-response The names of the fields used for headers differ depending on whether you enable multi-value headers for the target group. You must use multiValueHeaders if you have enabled multi-value headers and headers otherwise. **APIGW v1 Integration** https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-output-format If you specify values for both headers and multiValueHeaders, API Gateway merges them into a single list. If the same key-value pair is specified in both, only the values from multiValueHeaders will appear in the merged list. **APIGW v2 Integration** https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html Format 2.0 doesn't have multiValueHeaders or multiValueQueryStringParameters fields. Duplicate headers are combined with commas and included in the headers field. Duplicate query strings are combined with commas and included in the queryStringParameters field. **`awslabs/aws-lambda-go-api-proxy` source code** - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/response.go#L117 - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/responseALB.go#L108 - https://github.com/awslabs/aws-lambda-go-api-proxy/blob/3f6c8160ae0c22b0bd05b2e3a9122736f035c74b/core/responsev2.go#L117 --- lambda-http/src/response.rs | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index c1b48ef2..6a31cb79 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -69,8 +69,8 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers + // Explicitly empty, as API gateway v1 will merge "headers" and + // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, }), @@ -93,10 +93,10 @@ impl LambdaResponse { is_base64_encoded, status_code: status_code as i64, cookies, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers - headers: HeaderMap::new(), - multi_value_headers: headers, + // API gateway v2 doesn't have "multi_value_headers" field. Duplicate headers + // are combined with commas and included in the headers field. + headers, + multi_value_headers: HeaderMap::new(), }) } #[cfg(feature = "alb")] @@ -104,10 +104,9 @@ impl LambdaResponse { body, status_code: status_code as i64, is_base64_encoded, - // ALB responses are used for ALB integrations as well as - // Lambda Function URLs. The former uses the `multi_value_headers` field, - // while the later uses the `headers` field. We need to return - // both fields to ensure both integrations work correctly. + // ALB responses are used for ALB integration, which can be configured to use + // either "headers" or "multi_value_headers" field. We need to return both fields + // to ensure both configuration work correctly. headers: headers.clone(), multi_value_headers: headers, status_description: Some(format!( @@ -121,8 +120,8 @@ impl LambdaResponse { body, is_base64_encoded, status_code: status_code as i64, - // explicitly empty, as API gateway does not properly merge headers and - // multi-value-headers, resulting in duplicate headers + // Explicitly empty, as API gateway v1 will merge "headers" and + // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, }), @@ -475,7 +474,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-encoding":["gzip"]},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-encoding":"gzip"},"multiValueHeaders":{},"body":"MDAwMDAw","isBase64Encoded":true,"cookies":[]}"# ) } @@ -493,7 +492,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json"]},"body":"000000","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/json"},"multiValueHeaders":{},"body":"000000","isBase64Encoded":false,"cookies":[]}"# ) } @@ -511,7 +510,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/json; charset=utf-16"},"multiValueHeaders":{},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } @@ -529,7 +528,7 @@ mod tests { let json = serde_json::to_string(&response).expect("failed to serialize to json"); assert_eq!( json, - r#"{"statusCode":200,"headers":{},"multiValueHeaders":{"content-type":["application/graphql-response+json; charset=utf-16"]},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# + r#"{"statusCode":200,"headers":{"content-type":"application/graphql-response+json; charset=utf-16"},"multiValueHeaders":{},"body":"〰〰〰","isBase64Encoded":false,"cookies":[]}"# ) } From b60807efa6b6f5c7a3261e995342ab10dbd309ed Mon Sep 17 00:00:00 2001 From: Luke Ward Date: Fri, 24 May 2024 11:32:22 -0400 Subject: [PATCH 124/211] Update README.md (#881) updating docs to match changes in cargo lambda https://github.com/cargo-lambda/cargo-lambda/commit/3d8eff163aa6b8e7bd388ca3e67562b880334c95 --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 331635d2..c379f1dc 100644 --- a/README.md +++ b/README.md @@ -337,7 +337,7 @@ fn test_my_lambda_handler() { [Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project. You can run the following subcommand to compile your function(s) and start the server. ```bash -cargo lambda watch -a 127.0.0.1 -p 9001 +cargo lambda watch ``` Now you can use the `cargo lambda invoke` to send requests to your function. For example: @@ -358,7 +358,7 @@ An simpler alternative is to cURL the following endpoint based on the address an ```bash curl -v -X POST \ - 'http://127.0.0.1:9001/lambda-url//' \ + 'http://127.0.0.1:9000/lambda-url//' \ -H 'content-type: application/json' \ -d '{ "command": "hi" }' ``` From ee78eaa28fa4ebd8a464af226dc4fe8d26099eb6 Mon Sep 17 00:00:00 2001 From: Rene B <157360283+rebu-dt@users.noreply.github.com> Date: Tue, 28 May 2024 01:16:11 +0200 Subject: [PATCH 125/211] Fix extension compatibility issues with AWS Lambda Runtime Interface Emulator (#879) * fix: consider status codes 200-299 successful in Extension API * fix: Make `tracing` JSON field optional --- lambda-extension/src/events.rs | 3 ++- lambda-extension/src/extension.rs | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lambda-extension/src/events.rs b/lambda-extension/src/events.rs index c61fc9f0..db872d96 100644 --- a/lambda-extension/src/events.rs +++ b/lambda-extension/src/events.rs @@ -1,7 +1,7 @@ use serde::Deserialize; /// Request tracing information -#[derive(Debug, Deserialize)] +#[derive(Debug, Default, Deserialize)] #[serde(rename_all = "camelCase")] pub struct Tracing { /// The type of tracing exposed to the extension @@ -20,6 +20,7 @@ pub struct InvokeEvent { /// The function's Amazon Resource Name pub invoked_function_arn: String, /// The request tracing information + #[serde(default)] pub tracing: Tracing, } diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index cac1c7ec..e57940ae 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -272,7 +272,7 @@ where self.log_port_number, )?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to initialize the logs api: {}", res.status()); return Err(ExtensionError::boxed(err)); } @@ -318,7 +318,7 @@ where self.telemetry_port_number, )?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to initialize the telemetry api: {}", res.status()); return Err(ExtensionError::boxed(err)); } @@ -491,7 +491,7 @@ async fn register<'a>( let req = requests::register_request(&name, events)?; let res = client.call(req).await?; - if res.status() != http::StatusCode::OK { + if !res.status().is_success() { let err = format!("unable to register the extension: {}", res.status()); return Err(ExtensionError::boxed(err)); } From 7500b2b83528e8d323538472c6fe33d34ef99ca0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:53:22 -0700 Subject: [PATCH 126/211] Refresh readmes (#883) - Remove old tooling information that we don't test or use. - Update to recommend AL2023 - Remove any mention to AL1 - Cleanup code examples - Move lambda_http specific docs to its own readme Signed-off-by: David Calavera --- README.md | 118 +++--------------------------------------- lambda-http/README.md | 22 ++++++++ 2 files changed, 28 insertions(+), 112 deletions(-) diff --git a/README.md b/README.md index c379f1dc..32763e3e 100644 --- a/README.md +++ b/README.md @@ -87,25 +87,14 @@ By default, Cargo Lambda builds your functions to run on x86_64 architectures. I #### 1.2. Build your Lambda functions -__Amazon Linux 2__ +__Amazon Linux 2023__ -We recommend you to use Amazon Linux 2 runtimes (such as `provided.al2`) as much as possible for building Lambda functions in Rust. To build your Lambda functions for Amazon Linux 2 runtimes, run: +We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: ```bash cargo lambda build --release --arm64 ``` -__Amazon Linux 1__ - -Amazon Linux 1 uses glibc version 2.17, while Rust binaries need glibc version 2.18 or later by default. However, with Cargo Lambda, you can specify a different version of glibc. - -If you are building for Amazon Linux 1, or you want to support both Amazon Linux 2 and 1, run: - -```bash -cargo lambda build --release --target aarch64-unknown-linux-gnu.2.17 -``` -> **Note** -> Replace "aarch64" with "x86_64" if you are building for x86_64 ### 2. Deploying the binary to AWS Lambda For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html), AWS Lambda looks for an executable called `bootstrap` in the deployment package zip. Rename the generated executable to `bootstrap` and add it to a zip archive. @@ -117,8 +106,7 @@ You can find the `bootstrap` binary for your function under the `target/lambda` Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: ```bash -cargo lambda deploy \ - --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role +cargo lambda deploy ``` > **Warning** @@ -128,9 +116,7 @@ This command will create a Lambda function with the same name of your rust packa of the function by adding the argument at the end of the command: ```bash -cargo lambda deploy \ - --iam-role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ - my-first-lambda-function +cargo lambda deploy my-first-lambda-function ``` > **Note** @@ -162,7 +148,7 @@ You can find the resulting zip file in `target/lambda/YOUR_PACKAGE/bootstrap.zip $ aws lambda create-function --function-name rustTest \ --handler bootstrap \ --zip-file fileb://./target/lambda/basic/bootstrap.zip \ - --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 (or to provided.al for Amazon Linux 1). + --runtime provided.al2023 \ # Change this to provided.al2 if you would like to use Amazon Linux 2 --role arn:aws:iam::XXXXXXXXXXXXX:role/your_lambda_execution_role \ --environment Variables={RUST_BACKTRACE=1} \ --tracing-config Mode=Active @@ -229,74 +215,6 @@ $ aws lambda invoke $ cat output.json # Prints: {"msg": "Command Say Hi! executed."} ``` -#### 2.4. Serverless Framework - -Alternatively, you can build a Rust-based Lambda function declaratively using the [Serverless framework Rust plugin](https://github.com/softprops/serverless-rust). - -A number of getting started Serverless application templates exist to get you up and running quickly: - -- a minimal [echo function](https://github.com/softprops/serverless-aws-rust) to demonstrate what the smallest Rust function setup looks like -- a minimal [http function](https://github.com/softprops/serverless-aws-rust-http) to demonstrate how to interface with API Gateway using Rust's native [http](https://crates.io/crates/http) crate (note this will be a git dependency until 0.2 is published) -- a combination [multi function service](https://github.com/softprops/serverless-aws-rust-multi) to demonstrate how to set up a services with multiple independent functions - -Assuming your host machine has a relatively recent version of node, you [won't need to install any host-wide serverless dependencies](https://blog.npmjs.org/post/162869356040/introducing-npx-an-npm-package-runner). To get started, run the following commands to create a new lambda Rust application -and install project level dependencies. - -```bash -$ npx serverless install \ - --url https://github.com/softprops/serverless-aws-rust \ - --name my-new-app \ - && cd my-new-app \ - && npm install --silent -``` - -Deploy it using the standard serverless workflow: - -```bash -# build, package, and deploy service to aws lambda -$ npx serverless deploy -``` - -Invoke it using serverless framework or a configured AWS integrated trigger source: - -```bash -npx serverless invoke -f hello -d '{"foo":"bar"}' -``` - -#### 2.5. Docker - -Alternatively, you can build a Rust-based Lambda function in a [docker mirror of the AWS Lambda provided runtime with the Rust toolchain preinstalled](https://github.com/rust-serverless/lambda-rust). - -Running the following command will start an ephemeral docker container, which will build your Rust application and produce a zip file containing its binary auto-renamed to `bootstrap` to meet the AWS Lambda's expectations for binaries under `target/lambda_runtime/release/{your-binary-name}.zip`. Typically, this is just the name of your crate if you are using the cargo default binary (i.e. `main.rs`): - -```bash -# build and package deploy-ready artifact -$ docker run --rm \ - -v ${PWD}:/code \ - -v ${HOME}/.cargo/registry:/root/.cargo/registry \ - -v ${HOME}/.cargo/git:/root/.cargo/git \ - rustserverless/lambda-rust -``` - -With your application built and packaged, it's ready to ship to production. You can also invoke it locally to verify is behavior using the [lambci :provided docker container](https://hub.docker.com/r/lambci/lambda/), which is also a mirror of the AWS Lambda provided runtime with build dependencies omitted: - -```bash -# start a docker container replicating the "provided" lambda runtime -# awaiting an event to be provided via stdin -$ unzip -o \ - target/lambda/release/{your-binary-name}.zip \ - -d /tmp/lambda && \ - docker run \ - -i -e DOCKER_LAMBDA_USE_STDIN=1 \ - --rm \ - -v /tmp/lambda:/var/task \ - lambci/lambda:provided - -# provide an event payload via stdin (typically a json blob) - -# Ctrl-D to yield control back to your function -``` - ## Local development and testing ### Testing your code with unit and integration tests @@ -332,7 +250,7 @@ fn test_my_lambda_handler() { } ``` -### Cargo Lambda +### Local dev server with Cargo Lambda [Cargo Lambda](https://www.cargo-lambda.info) provides a local server that emulates the AWS Lambda control plane. This server works on Windows, Linux, and MacOS. In the root of your Lambda project. You can run the following subcommand to compile your function(s) and start the server. @@ -432,30 +350,6 @@ fn main() -> Result<(), Box> { } ``` -## Feature flags in lambda_http - -`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions. - -By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags. - -The available features flags for `lambda_http` are the following: - -- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). -- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). -- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). -- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). - -If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable: - -```toml -[dependencies.lambda_http] -version = "0.5.3" -default-features = false -features = ["apigw_rest"] -``` - -This will make your function compile much faster. - ## Supported Rust Versions (MSRV) The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. diff --git a/lambda-http/README.md b/lambda-http/README.md index 79c3410d..6b394964 100644 --- a/lambda-http/README.md +++ b/lambda-http/README.md @@ -235,3 +235,25 @@ pub async fn function_handler(dynamodb_client: &aws_sdk_dynamodb::Client, event: When you integrate HTTP Lambda functions with API Gateway stages, the path received in the request will include the stage as the first segment, for example `/production/api/v1`, where `production` is the API Gateway stage. If you don't want to receive the stage as part of the path, you can set the environment variable `AWS_LAMBDA_HTTP_IGNORE_STAGE_IN_PATH` to `true`, either in your Lambda function configuration, or inside the `main` Rust function. Following the previous example, when this environment variable is present, the path that the function receives is `/api/v1`, eliminating the stage from the first segment. + +## Feature flags + +`lambda_http` is a wrapper for HTTP events coming from three different services, Amazon Load Balancer (ALB), Amazon Api Gateway (APIGW), and AWS Lambda Function URLs. Amazon Api Gateway can also send events from three different endpoints, REST APIs, HTTP APIs, and WebSockets. `lambda_http` transforms events from all these sources into native `http::Request` objects, so you can incorporate Rust HTTP semantics into your Lambda functions. + +By default, `lambda_http` compiles your function to support any of those services. This increases the compile time of your function because we have to generate code for all the sources. In reality, you'll usually put a Lambda function only behind one of those sources. You can choose which source to generate code for with feature flags. + +The available features flags for `lambda_http` are the following: + +- `alb`: for events coming from [Amazon Elastic Load Balancer](https://aws.amazon.com/elasticloadbalancing/). +- `apigw_rest`: for events coming from [Amazon API Gateway Rest APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html). +- `apigw_http`: for events coming from [Amazon API Gateway HTTP APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) and [AWS Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html). +- `apigw_websockets`: for events coming from [Amazon API Gateway WebSockets](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-websocket-api.html). + +If you only want to support one of these sources, you can disable the default features, and enable only the source that you care about in your package's `Cargo.toml` file. Substitute the dependency line for `lambda_http` for the snippet below, changing the feature that you want to enable: + +```toml +[dependencies.lambda_http] +version = "0.5.3" +default-features = false +features = ["apigw_rest"] +``` From 00d822eacbad4e4794576bcfb4155971215a85d6 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:54:44 -0700 Subject: [PATCH 127/211] Fix GLIBC typo (#885) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32763e3e..b4a5387f 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ By default, Cargo Lambda builds your functions to run on x86_64 architectures. I __Amazon Linux 2023__ -We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: +We recommend you to use the Amazon Linux 2023 (such as `provided.al2023`) because it includes a newer version of GLIBC, which many Rust programs depend on. To build your Lambda functions for Amazon Linux 2023 runtimes, run: ```bash cargo lambda build --release --arm64 From 31250f3ff0ffbee83f2d907d707208e7e107dc02 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 31 May 2024 08:57:07 -0700 Subject: [PATCH 128/211] Add Cognito Post Confirmation example (#884) - Show how to work with Cognito's Post Confirmation events. - Make response generic so customers can return a different object as reponse. Signed-off-by: David Calavera --- .../.gitignore | 1 + .../Cargo.toml | 24 ++++++++ .../basic-cognito-post-confirmation/README.md | 15 +++++ .../src/main.rs | 60 +++++++++++++++++++ lambda-events/src/event/cognito/mod.rs | 10 +++- 5 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 examples/basic-cognito-post-confirmation/.gitignore create mode 100644 examples/basic-cognito-post-confirmation/Cargo.toml create mode 100644 examples/basic-cognito-post-confirmation/README.md create mode 100644 examples/basic-cognito-post-confirmation/src/main.rs diff --git a/examples/basic-cognito-post-confirmation/.gitignore b/examples/basic-cognito-post-confirmation/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/basic-cognito-post-confirmation/Cargo.toml b/examples/basic-cognito-post-confirmation/Cargo.toml new file mode 100644 index 00000000..7d2e7ab4 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "basic-cognito-post-confirmation" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +aws-config = "1.5.0" +aws-sdk-ses = "1.28.0" +aws_lambda_events = { path = "../../lambda-events", default-features = false, features = ["cognito"] } + +lambda_runtime = { path = "../../lambda-runtime" } +tokio = { version = "1", features = ["macros"] } + diff --git a/examples/basic-cognito-post-confirmation/README.md b/examples/basic-cognito-post-confirmation/README.md new file mode 100644 index 00000000..4391e16c --- /dev/null +++ b/examples/basic-cognito-post-confirmation/README.md @@ -0,0 +1,15 @@ +# Cognito Post Confirmation Request example + +This example shows how to write a Lambda function in Rust to process Cognito's Post Confirmation requests. + +This is a translation of the example in the AWS Docs to Rust: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-post-confirmation.html#aws-lambda-triggers-post-confirmation-example + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-cognito-post-confirmation/src/main.rs b/examples/basic-cognito-post-confirmation/src/main.rs new file mode 100644 index 00000000..178bd3a9 --- /dev/null +++ b/examples/basic-cognito-post-confirmation/src/main.rs @@ -0,0 +1,60 @@ +use aws_config::BehaviorVersion; +use aws_lambda_events::event::cognito::CognitoEventUserPoolsPostConfirmation; +use aws_sdk_ses::{ + types::{Body, Content, Destination, Message}, + Client, +}; +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; + +const SOURCE_EMAIL: &str = ""; + +async fn function_handler( + client: &aws_sdk_ses::Client, + event: LambdaEvent, +) -> Result { + let payload = event.payload; + + if let Some(email) = payload.request.user_attributes.get("email") { + let body = if let Some(name) = payload.request.user_attributes.get("name") { + format!("Welcome {name}, you have been confirmed.") + } else { + "Welcome, you have been confirmed.".to_string() + }; + send_post_confirmation_email(client, email, "Cognito Identity Provider registration completed", &body).await?; + } + + // Cognito always expect a response with the same shape as + // the event when it handles Post Confirmation triggers. + Ok(payload) +} + +async fn send_post_confirmation_email(client: &Client, email: &str, subject: &str, body: &str) -> Result<(), Error> { + let destination = Destination::builder().to_addresses(email).build(); + let subject = Content::builder().data(subject).build()?; + let body = Content::builder().data(body).build()?; + + let message = Message::builder() + .body(Body::builder().text(body).build()) + .subject(subject) + .build(); + + client + .send_email() + .source(SOURCE_EMAIL) + .destination(destination) + .message(message) + .send() + .await?; + + Ok(()) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = Client::new(&config); + + run(service_fn(|event| function_handler(&client, event))).await +} diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 6eb1c001..1b31abcc 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -84,13 +84,18 @@ pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { /// allowing the Lambda to send custom messages or add custom logic. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct CognitoEventUserPoolsPostConfirmation { +pub struct CognitoEventUserPoolsPostConfirmation +where + T: DeserializeOwned, + T: Serialize, +{ #[serde(rename = "CognitoEventUserPoolsHeader")] #[serde(flatten)] pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostConfirmationRequest, - pub response: CognitoEventUserPoolsPostConfirmationResponse, + #[serde(bound = "")] + pub response: T, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -254,6 +259,7 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostConfirmationResponse {} + /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] From 0fcba1641aca40d1951033efcf710fc05f6f8e5b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jun 2024 10:08:23 -0700 Subject: [PATCH 129/211] Improve Kinesis event recort type (#886) Make the encryption type an enum. Make sequence number and partition key non-optional. Signed-off-by: David Calavera --- lambda-events/src/event/kinesis/event.rs | 29 +++++++++++++-- .../example-kinesis-event-encrypted.json | 37 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) create mode 100644 lambda-events/src/fixtures/example-kinesis-event-encrypted.json diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 2b14cbed..94ebbbf9 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -62,15 +62,24 @@ pub struct KinesisEventRecord { pub struct KinesisRecord { pub approximate_arrival_timestamp: SecondTimestamp, pub data: Base64Data, - pub encryption_type: Option, #[serde(default)] - pub partition_key: Option, + pub encryption_type: KinesisEncryptionType, #[serde(default)] - pub sequence_number: Option, + pub partition_key: String, + #[serde(default)] + pub sequence_number: String, #[serde(default)] pub kinesis_schema_version: Option, } +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum KinesisEncryptionType { + #[default] + None, + Kms, +} + #[cfg(test)] mod test { use super::*; @@ -80,6 +89,20 @@ mod test { fn example_kinesis_event() { let data = include_bytes!("../../fixtures/example-kinesis-event.json"); let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(KinesisEncryptionType::None, parsed.records[0].kinesis.encryption_type); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "kinesis")] + fn example_kinesis_event_encrypted() { + let data = include_bytes!("../../fixtures/example-kinesis-event-encrypted.json"); + let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(KinesisEncryptionType::Kms, parsed.records[0].kinesis.encryption_type); + let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); diff --git a/lambda-events/src/fixtures/example-kinesis-event-encrypted.json b/lambda-events/src/fixtures/example-kinesis-event-encrypted.json new file mode 100644 index 00000000..7d1697b1 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-event-encrypted.json @@ -0,0 +1,37 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333333333333333333333333333333333333333333", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480641523.477, + "encryptionType": "KMS" + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333333333333333333333333333333333333333333", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + }, + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333334444444444444444444444444444444444444", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480841523.477 + }, + "eventSource": "aws:kinesis", + "eventVersion": "1.0", + "eventID": "shardId-000000000000:49568167373333333334444444444444444444444444444444444444", + "eventName": "aws:kinesis:record", + "invokeIdentityArn": "arn:aws:iam::123456789012:role/LambdaRole", + "awsRegion": "us-east-1", + "eventSourceARN": "arn:aws:kinesis:us-east-1:123456789012:stream/simple-stream" + } + ] +} \ No newline at end of file From 1cf868c7a874be371d0bf5a18a30996c638942fc Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 2 Jun 2024 10:08:58 -0700 Subject: [PATCH 130/211] Add support for Lambda-Extesion-Accept-Feature header (#887) - Use this header to read the account id that the extesion is installed in. - Keep the account id as optional just in case we make this feature optional in the future. - Users should not rely on it being always present. - Extract all the information that the register call provides. --- lambda-extension/src/extension.rs | 60 ++++++++++++++++++++++++++----- lambda-extension/src/requests.rs | 6 ++++ 2 files changed, 58 insertions(+), 8 deletions(-) diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index e57940ae..3800f7ab 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -6,6 +6,7 @@ use hyper::service::service_fn; use hyper_util::rt::tokio::TokioIo; use lambda_runtime_api_client::Client; +use serde::Deserialize; use std::{ convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, }; @@ -230,8 +231,7 @@ where pub async fn register(self) -> Result, Error> { let client = &Client::builder().build()?; - let extension_id = register(client, self.extension_name, self.events).await?; - let extension_id = extension_id.to_str()?; + let register_res = register(client, self.extension_name, self.events).await?; // Logs API subscriptions must be requested during the Lambda init phase (see // https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-subscribing). @@ -266,7 +266,7 @@ where // Call Logs API to start receiving events let req = requests::subscribe_request( Api::LogsApi, - extension_id, + ®ister_res.extension_id, self.log_types, self.log_buffering, self.log_port_number, @@ -312,7 +312,7 @@ where // Call Telemetry API to start receiving events let req = requests::subscribe_request( Api::TelemetryApi, - extension_id, + ®ister_res.extension_id, self.telemetry_types, self.telemetry_buffering, self.telemetry_port_number, @@ -326,7 +326,11 @@ where } Ok(RegisteredExtension { - extension_id: extension_id.to_string(), + extension_id: register_res.extension_id, + function_name: register_res.function_name, + function_version: register_res.function_version, + handler: register_res.handler, + account_id: register_res.account_id, events_processor: self.events_processor, }) } @@ -339,7 +343,17 @@ where /// An extension registered by calling [`Extension::register`]. pub struct RegisteredExtension { - extension_id: String, + /// The ID of the registered extension. This ID is unique per extension and remains constant + pub extension_id: String, + /// The ID of the account the extension was registered to. + /// This will be `None` if the register request doesn't send the Lambda-Extension-Accept-Feature header + pub account_id: Option, + /// The name of the Lambda function that the extension is registered with + pub function_name: String, + /// The version of the Lambda function that the extension is registered with + pub function_version: String, + /// The Lambda function handler that AWS Lambda invokes + pub handler: String, events_processor: E, } @@ -468,12 +482,30 @@ where } } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct RegisterResponseBody { + function_name: String, + function_version: String, + handler: String, + account_id: Option, +} + +#[derive(Debug)] +struct RegisterResponse { + extension_id: String, + function_name: String, + function_version: String, + handler: String, + account_id: Option, +} + /// Initialize and register the extension in the Extensions API async fn register<'a>( client: &'a Client, extension_name: Option<&'a str>, events: Option<&'a [&'a str]>, -) -> Result { +) -> Result { let name = match extension_name { Some(name) => name.into(), None => { @@ -501,5 +533,17 @@ async fn register<'a>( .get(requests::EXTENSION_ID_HEADER) .ok_or_else(|| ExtensionError::boxed("missing extension id header")) .map_err(|e| ExtensionError::boxed(e.to_string()))?; - Ok(header.clone()) + let extension_id = header.to_str()?.to_string(); + + let (_, body) = res.into_parts(); + let body = body.collect().await?.to_bytes(); + let response: RegisterResponseBody = serde_json::from_slice(&body)?; + + Ok(RegisterResponse { + extension_id, + function_name: response.function_name, + function_version: response.function_version, + handler: response.handler, + account_id: response.account_id, + }) } diff --git a/lambda-extension/src/requests.rs b/lambda-extension/src/requests.rs index 4d5f1527..522b8402 100644 --- a/lambda-extension/src/requests.rs +++ b/lambda-extension/src/requests.rs @@ -9,6 +9,11 @@ const EXTENSION_ERROR_TYPE_HEADER: &str = "Lambda-Extension-Function-Error-Type" const CONTENT_TYPE_HEADER_NAME: &str = "Content-Type"; const CONTENT_TYPE_HEADER_VALUE: &str = "application/json"; +// Comma separated list of features the extension supports. +// `accountId` is currently the only supported feature. +const EXTENSION_ACCEPT_FEATURE: &str = "Lambda-Extension-Accept-Feature"; +const EXTENSION_ACCEPT_FEATURE_VALUE: &str = "accountId"; + pub(crate) fn next_event_request(extension_id: &str) -> Result, Error> { let req = build_request() .method(Method::GET) @@ -25,6 +30,7 @@ pub(crate) fn register_request(extension_name: &str, events: &[&str]) -> Result< .method(Method::POST) .uri("/2020-01-01/extension/register") .header(EXTENSION_NAME_HEADER, extension_name) + .header(EXTENSION_ACCEPT_FEATURE, EXTENSION_ACCEPT_FEATURE_VALUE) .header(CONTENT_TYPE_HEADER_NAME, CONTENT_TYPE_HEADER_VALUE) .body(Body::from(serde_json::to_string(&events)?))?; From d03bbc4caafc3f58abe84bd0c941c3f65b91f68b Mon Sep 17 00:00:00 2001 From: stelo <42366677+jfkisafk@users.noreply.github.com> Date: Thu, 6 Jun 2024 08:07:34 -0700 Subject: [PATCH 131/211] fix(events): makes ApproximateCreationDateTime optional in dynamo event stream record (#891) closes https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 --- lambda-events/src/event/dynamodb/mod.rs | 6 +++++- .../example-dynamodb-event-record-with-optional-fields.json | 1 - 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 33b977bd..77cc77d2 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -213,9 +213,11 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX - /// epoch time (http://www.epochconverter.com/) format. + /// epoch time (http://www.epochconverter.com/) format. Might not be present in + /// the record: https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 #[serde(rename = "ApproximateCreationDateTime")] #[serde(with = "float_unix_epoch")] + #[serde(default)] pub approximate_creation_date_time: DateTime, /// The primary key attribute(s) for the DynamoDB item that was modified. #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] @@ -274,5 +276,7 @@ mod test { let output: String = serde_json::to_string(&parsed).unwrap(); let reparsed: EventRecord = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); + let date = Utc.timestamp_micros(0).unwrap(); // 1970-01-01T00:00:00Z + assert_eq!(date, reparsed.change.approximate_creation_date_time); } } diff --git a/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json index 873cccce..4f468c70 100644 --- a/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json +++ b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json @@ -6,7 +6,6 @@ "recordFormat":"application/json", "tableName":"examples", "dynamodb":{ - "ApproximateCreationDateTime":1649809356015, "Keys":{ "id":{ "S":"00000000-0000-0000-0000-000000000000" From 85d1cb8b196262213f3bbc53b0e0daebf471b2bb Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 8 Jun 2024 11:36:43 -0700 Subject: [PATCH 132/211] Log function error (#892) Bring back this log that was lost between version 0.10 and 0.11. --- lambda-runtime/src/layers/panic.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 26ceeecc..6600b743 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -58,9 +58,9 @@ where let fut = AssertUnwindSafe(task).catch_unwind(); CatchPanicFuture::Future(fut, PhantomData) } - Err(err) => { - error!(error = ?err, "user handler panicked"); - CatchPanicFuture::Error(err) + Err(error) => { + error!(?error, "user handler panicked"); + CatchPanicFuture::Error(error) } } } @@ -85,15 +85,19 @@ where match self.project() { CatchPanicFutureProj::Future(fut, _) => match fut.poll(cx) { Poll::Ready(ready) => match ready { - Ok(inner_result) => Poll::Ready(inner_result.map_err(|err| err.into())), - Err(err) => { - error!(error = ?err, "user handler panicked"); - Poll::Ready(Err(Self::build_panic_diagnostic(&err))) + Ok(Ok(success)) => Poll::Ready(Ok(success)), + Ok(Err(error)) => { + error!("{error:?}"); + Poll::Ready(Err(error.into())) + } + Err(error) => { + error!(?error, "user handler panicked"); + Poll::Ready(Err(Self::build_panic_diagnostic(&error))) } }, Poll::Pending => Poll::Pending, }, - CatchPanicFutureProj::Error(err) => Poll::Ready(Err(Self::build_panic_diagnostic(err))), + CatchPanicFutureProj::Error(error) => Poll::Ready(Err(Self::build_panic_diagnostic(error))), } } } From 6382a85137b033019c78f963b77d1b75399b7f34 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 13 Jun 2024 21:05:48 -0700 Subject: [PATCH 133/211] Release new patch versions for 0.11 (#893) * Release new patch versions for 0.11 Signed-off-by: David Calavera * Fix clippy errors from Rust 1.79.0 Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime-api-client/src/body/channel.rs | 4 ++-- lambda-runtime/Cargo.toml | 7 ++----- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9bd5923e..28b0f7e0 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.3" +version = "0.11.4" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.11.3", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index 815de5f2..f5fcb77b 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -20,8 +20,8 @@ pub use sender::Sender; pub(crate) struct DecodedLength(u64); impl DecodedLength { - pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX); - pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1); + pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(u64::MAX); + pub(crate) const CHUNKED: DecodedLength = DecodedLength(u64::MAX - 1); pub(crate) const ZERO: DecodedLength = DecodedLength(0); pub(crate) fn sub_if(&mut self, amt: u64) { diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 1883a18d..652381eb 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.2" +version = "0.11.3" authors = [ "David Calavera ", "Harold Sun ", @@ -26,10 +26,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } -hyper = { workspace = true, features = [ - "http1", - "client", -] } +hyper = { workspace = true, features = ["http1", "client"] } hyper-util = { workspace = true, features = [ "client", "client-legacy", From d38b1a4d2b96de67923d35e1bb4d3293d6b270f5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:14:53 -0700 Subject: [PATCH 134/211] Bump braces from 3.0.2 to 3.0.3 in /examples/http-axum/cdk (#894) Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3. - [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md) - [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3) --- updated-dependencies: - dependency-name: braces dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index c35c0bf4..9a7e3026 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -1778,12 +1778,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -2346,9 +2346,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -5739,12 +5739,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browserslist": { @@ -6138,9 +6138,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" From 175acb64d5fcd498785fd1798f4a103a43dcfca7 Mon Sep 17 00:00:00 2001 From: Jon Smith <63128850+jonjsmith@users.noreply.github.com> Date: Sun, 16 Jun 2024 15:51:57 -0500 Subject: [PATCH 135/211] Update README.md (#895) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index b4a5387f..0a19e467 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ There are other ways of building your function: manually with the AWS CLI, with By default, Cargo Lambda builds your functions to run on x86_64 architectures. If you'd like to use a different architecture, use the options described below. -#### 1.2. Build your Lambda functions +#### 1.1. Build your Lambda functions __Amazon Linux 2023__ @@ -101,7 +101,7 @@ For [a custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-cus You can find the `bootstrap` binary for your function under the `target/lambda` directory. -#### 2.2. Deploying with Cargo Lambda +#### 2.1. Deploying with Cargo Lambda Once you've built your code with one of the options described earlier, use the `deploy` subcommand to upload your function to AWS: From 92cdd74b2aa4b5397f7ff4f1800b54c9b949d96a Mon Sep 17 00:00:00 2001 From: Tethys Svensson Date: Wed, 19 Jun 2024 18:32:44 +0200 Subject: [PATCH 136/211] Make child-spans work as expected when using the lambda-runtime (#896) --- lambda-runtime/src/layers/trace.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 7a9f8370..35c74c12 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -43,7 +43,11 @@ where fn call(&mut self, req: LambdaInvocation) -> Self::Future { let span = request_span(&req.context); - self.inner.call(req).instrument(span) + let future = { + let _guard = span.enter(); + self.inner.call(req) + }; + future.instrument(span) } } From cc239ea4aeffc51cfe63fbbb151c33ec49734645 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 24 Jun 2024 19:57:50 -0700 Subject: [PATCH 137/211] Relax blanket implementation of Diagnostic (#897) Instead of implementing Diagnostic for everything that implements Display, implement the trait only for a few well known types. This gives people more flexibility to implement Diagnostic. Signed-off-by: David Calavera --- Cargo.toml | 6 +- .../Cargo.toml | 1 + examples/basic-error-diagnostic/.gitignore | 1 + examples/basic-error-diagnostic/Cargo.toml | 22 +++ examples/basic-error-diagnostic/README.md | 13 ++ examples/basic-error-diagnostic/src/main.rs | 37 +++++ examples/basic-error-handling/README.md | 4 +- examples/basic-error-handling/src/main.rs | 8 +- lambda-events/Cargo.toml | 6 +- lambda-http/src/lib.rs | 3 +- lambda-http/src/streaming.rs | 5 +- lambda-runtime/src/diagnostic.rs | 141 ++++++++++++++++++ lambda-runtime/src/layers/api_response.rs | 11 +- lambda-runtime/src/lib.rs | 10 +- lambda-runtime/src/requests.rs | 3 +- lambda-runtime/src/runtime.rs | 9 +- lambda-runtime/src/types.rs | 81 +--------- 17 files changed, 251 insertions(+), 110 deletions(-) create mode 100644 examples/basic-error-diagnostic/.gitignore create mode 100644 examples/basic-error-diagnostic/Cargo.toml create mode 100644 examples/basic-error-diagnostic/README.md create mode 100644 examples/basic-error-diagnostic/src/main.rs create mode 100644 lambda-runtime/src/diagnostic.rs diff --git a/Cargo.toml b/Cargo.toml index 0b01ee05..c55f0e60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,11 @@ exclude = ["examples"] [workspace.dependencies] base64 = "0.22" bytes = "1" -chrono = "0.4.35" +chrono = { version = "0.4.35", default-features = false, features = [ + "clock", + "serde", + "std", +] } futures = "0.3" futures-channel = "0.3" futures-util = "0.3" diff --git a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml index 116ab8ef..bd41a01a 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/Cargo.toml @@ -1,4 +1,5 @@ [workspace] +resolver = "2" members = [ "producer", diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-diagnostic/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/basic-error-diagnostic/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-diagnostic/Cargo.toml new file mode 100644 index 00000000..b81ef730 --- /dev/null +++ b/examples/basic-error-diagnostic/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "basic-error-diagnostic" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] + +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +thiserror = "1.0.61" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-diagnostic/README.md new file mode 100644 index 00000000..b9bf1827 --- /dev/null +++ b/examples/basic-error-diagnostic/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function Error handling example + +This example shows how to implement the `Diagnostic` trait to return a specific `error_type` in the Lambda error response. If you don't use the `error_type` field, you don't need to implement `Diagnostic`, the type will be generated based on the error type name. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-diagnostic/src/main.rs new file mode 100644 index 00000000..11f68d4b --- /dev/null +++ b/examples/basic-error-diagnostic/src/main.rs @@ -0,0 +1,37 @@ +use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent}; +use serde::Deserialize; +use thiserror; + +#[derive(Deserialize)] +struct Request {} + +#[derive(Debug, thiserror::Error)] +pub enum ExecutionError { + #[error("transient database error: {0}")] + DatabaseError(String), + #[error("unexpected error: {0}")] + Unexpected(String), +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: ExecutionError) -> Diagnostic<'a> { + let (error_type, error_message) = match value { + ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()), + ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()), + }; + Diagnostic { + error_type: error_type.into(), + error_message: error_message.into(), + } + } +} + +/// This is the main body for the Lambda function +async fn function_handler(_event: LambdaEvent) -> Result<(), ExecutionError> { + Err(ExecutionError::Unexpected("ooops".to_string())) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_runtime::run(service_fn(function_handler)).await +} diff --git a/examples/basic-error-handling/README.md b/examples/basic-error-handling/README.md index 498f8a50..5eef4207 100644 --- a/examples/basic-error-handling/README.md +++ b/examples/basic-error-handling/README.md @@ -1,4 +1,6 @@ -# AWS Lambda Function example +# AWS Lambda Function Error handling example + +This example shows how to return a custom error type for unexpected failures. ## Build & Deploy diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 528d6f02..3bc76936 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,7 +1,7 @@ /// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; -use serde_json::{json, Value}; +use serde_json::json; use std::fs::File; /// A simple Lambda request structure with just one field @@ -59,11 +59,11 @@ async fn main() -> Result<(), Error> { } /// The actual handler of the Lambda request. -pub(crate) async fn func(event: LambdaEvent) -> Result { +pub(crate) async fn func(event: LambdaEvent) -> Result { let (event, ctx) = event.into_parts(); // check what action was requested - match serde_json::from_value::(event)?.event_type { + match event.event_type { EventType::SimpleError => { // generate a simple text message error using `simple_error` crate return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))); @@ -94,7 +94,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result { msg: "OK".into(), }; - return Ok(json!(resp)); + return Ok(resp); } } } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 6d496b43..d9774104 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -18,11 +18,7 @@ edition = "2021" [dependencies] base64 = { workspace = true } bytes = { workspace = true, features = ["serde"], optional = true } -chrono = { workspace = true, default-features = false, features = [ - "clock", - "serde", - "std", -], optional = true } +chrono = { workspace = true, optional = true } flate2 = { version = "1.0.24", optional = true } http = { workspace = true, optional = true } http-body = { workspace = true, optional = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 265f5ef0..90e59867 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -68,6 +68,7 @@ pub use http::{self, Response}; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] pub use lambda_runtime::tracing; +use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service}; use request::RequestFuture; use response::ResponseFuture; @@ -193,7 +194,7 @@ where S: Service, S::Future: Send + 'a, R: IntoResponse, - E: std::fmt::Debug + std::fmt::Display, + E: std::fmt::Debug + for<'b> Into>, { lambda_runtime::run(Adapter::from(handler)).await } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 217c4564..df569129 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -5,8 +5,9 @@ use crate::{request::LambdaRequest, RequestExt}; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; +use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use std::pin::Pin; use std::task::{Context, Poll}; use tokio_stream::Stream; @@ -20,7 +21,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), where S: Service, Error = E>, S::Future: Send + 'a, - E: Debug + Display, + E: Debug + for<'b> Into>, B: Body + Unpin + Send + 'static, B::Data: Into + Send, B::Error: Into + Send + Debug, diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs new file mode 100644 index 00000000..bc9ba623 --- /dev/null +++ b/lambda-runtime/src/diagnostic.rs @@ -0,0 +1,141 @@ +use serde::{Deserialize, Serialize}; +use std::{any::type_name, borrow::Cow}; + +use crate::{deserializer::DeserializeError, Error}; + +/// Diagnostic information about an error. +/// +/// `Diagnostic` is automatically derived for some common types, +/// like boxed types that implement [`Error`][std::error::Error]. +/// +/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of +/// the original error with [`std::any::type_name`] as a fallback, which may +/// not be reliable for conditional error handling. +/// You can define your own error container that implements `Into` +/// if you need to handle errors based on error types. +/// +/// Example: +/// ``` +/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; +/// use std::borrow::Cow; +/// +/// #[derive(Debug)] +/// struct ErrorResponse(Error); +/// +/// impl<'a> Into> for ErrorResponse { +/// fn into(self) -> Diagnostic<'a> { +/// Diagnostic { +/// error_type: "MyError".into(), +/// error_message: self.0.to_string().into(), +/// } +/// } +/// } +/// +/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { +/// // ... do something +/// Ok(()) +/// } +/// ``` +#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Diagnostic<'a> { + /// Error type. + /// + /// `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`] as a fallback. + /// Please implement your own `Into` if you need more reliable + /// error types. + pub error_type: Cow<'a, str>, + /// Error message. + /// + /// `error_message` is the output from the [`Display`][std::fmt::Display] + /// implementation of the original error as a fallback. + pub error_message: Cow<'a, str>, +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: DeserializeError) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: Error) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a, T> From> for Diagnostic<'a> +where + T: std::error::Error, +{ + fn from(value: Box) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From> for Diagnostic<'a> { + fn from(value: Box) -> Self { + Diagnostic { + error_type: type_name::>().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: std::convert::Infallible) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.to_string().into(), + } + } +} + +impl<'a> From for Diagnostic<'a> { + fn from(value: String) -> Self { + Diagnostic { + error_type: type_name::().into(), + error_message: value.into(), + } + } +} + +impl<'a> From<&'static str> for Diagnostic<'a> { + fn from(value: &'static str) -> Self { + Diagnostic { + error_type: type_name::<&'static str>().into(), + error_message: value.into(), + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn round_trip_lambda_error() { + use serde_json::{json, Value}; + let expected = json!({ + "errorType": "InvalidEventDataError", + "errorMessage": "Error parsing event data.", + }); + + let actual = Diagnostic { + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data.".into(), + }; + let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); + assert_eq!(expected, actual); + } +} diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 266402cf..55942d0b 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -1,8 +1,9 @@ -use crate::requests::{EventCompletionRequest, IntoRequest}; -use crate::runtime::LambdaInvocation; -use crate::types::Diagnostic; -use crate::{deserializer, IntoFunctionResponse}; -use crate::{EventErrorRequest, LambdaEvent}; +use crate::{ + deserializer, + requests::{EventCompletionRequest, IntoRequest}, + runtime::LambdaInvocation, + Diagnostic, EventErrorRequest, IntoFunctionResponse, LambdaEvent, +}; use futures::ready; use futures::Stream; use lambda_runtime_api_client::{body::Body, BoxError}; diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 9638df64..c3e68cc4 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -18,8 +18,12 @@ use tokio_stream::Stream; use tower::util::ServiceFn; pub use tower::{self, service_fn, Service}; +/// Diagnostic utilities to convert Rust types into Lambda Error types. +pub mod diagnostic; +pub use diagnostic::Diagnostic; + mod deserializer; -/// Tower middleware to be applied to runtime invocatinos. +/// Tower middleware to be applied to runtime invocations. pub mod layers; mod requests; mod runtime; @@ -35,9 +39,7 @@ mod types; use requests::EventErrorRequest; pub use runtime::{LambdaInvocation, Runtime}; -pub use types::{ - Context, Diagnostic, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse, -}; +pub use types::{Context, FunctionResponse, IntoFunctionResponse, LambdaEvent, MetadataPrelude, StreamResponse}; /// Error type that lambdas may result in pub type Error = lambda_runtime_api_client::BoxError; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index ec893710..de0835c7 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,5 +1,4 @@ -use crate::types::ToStreamErrorTrailer; -use crate::{types::Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; +use crate::{types::ToStreamErrorTrailer, Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; use http::header::CONTENT_TYPE; use http::{Method, Request, Uri}; diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 5ded610c..92ba5f47 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -1,7 +1,7 @@ -use super::requests::{IntoRequest, NextEventRequest}; -use super::types::{invoke_request_id, Diagnostic, IntoFunctionResponse, LambdaEvent}; use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; -use crate::{Config, Context}; +use crate::requests::{IntoRequest, NextEventRequest}; +use crate::types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}; +use crate::{Config, Context, Diagnostic}; use http_body_util::BodyExt; use lambda_runtime_api_client::BoxError; use lambda_runtime_api_client::Client as ApiClient; @@ -252,8 +252,7 @@ mod endpoint_tests { use super::{incoming, wrap_handler}; use crate::{ requests::{EventCompletionRequest, EventErrorRequest, IntoRequest, NextEventRequest}, - types::Diagnostic, - Config, Error, Runtime, + Config, Diagnostic, Error, Runtime, }; use futures::future::BoxFuture; use http::{HeaderValue, StatusCode}; diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index b4f10f71..ee09978f 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -5,75 +5,12 @@ use http::{header::ToStrError, HeaderMap, HeaderValue, StatusCode}; use lambda_runtime_api_client::body::Body; use serde::{Deserialize, Serialize}; use std::{ - borrow::Cow, collections::HashMap, - fmt::{Debug, Display}, + fmt::Debug, time::{Duration, SystemTime}, }; use tokio_stream::Stream; -/// Diagnostic information about an error. -/// -/// `Diagnostic` is automatically derived for types that implement -/// [`Display`][std::fmt::Display]; e.g., [`Error`][std::error::Error]. -/// -/// [`error_type`][`Diagnostic::error_type`] is derived from the type name of -/// the original error with [`std::any::type_name`] as a fallback, which may -/// not be reliable for conditional error handling. -/// You can define your own error container that implements `Into` -/// if you need to handle errors based on error types. -/// -/// Example: -/// ``` -/// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; -/// use std::borrow::Cow; -/// -/// #[derive(Debug)] -/// struct ErrorResponse(Error); -/// -/// impl<'a> Into> for ErrorResponse { -/// fn into(self) -> Diagnostic<'a> { -/// Diagnostic { -/// error_type: Cow::Borrowed("MyError"), -/// error_message: Cow::Owned(self.0.to_string()), -/// } -/// } -/// } -/// -/// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { -/// // ... do something -/// Ok(()) -/// } -/// ``` -#[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Diagnostic<'a> { - /// Error type. - /// - /// `error_type` is derived from the type name of the original error with - /// [`std::any::type_name`] as a fallback. - /// Please implement your own `Into` if you need more reliable - /// error types. - pub error_type: Cow<'a, str>, - /// Error message. - /// - /// `error_message` is the output from the [`Display`][std::fmt::Display] - /// implementation of the original error as a fallback. - pub error_message: Cow<'a, str>, -} - -impl<'a, T> From for Diagnostic<'a> -where - T: Display, -{ - fn from(value: T) -> Self { - Diagnostic { - error_type: Cow::Borrowed(std::any::type_name::()), - error_message: Cow::Owned(format!("{value}")), - } - } -} - /// Client context sent by the AWS Mobile SDK. #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] pub struct ClientContext { @@ -342,22 +279,6 @@ mod test { use crate::Config; use std::sync::Arc; - #[test] - fn round_trip_lambda_error() { - use serde_json::{json, Value}; - let expected = json!({ - "errorType": "InvalidEventDataError", - "errorMessage": "Error parsing event data.", - }); - - let actual = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data."), - }; - let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); - assert_eq!(expected, actual); - } - #[test] fn context_with_expected_values_and_types_resolves() { let config = Arc::new(Config::default()); From f484569adcea8701a7f7e358ce1fc08b3b67059e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:07:46 -0700 Subject: [PATCH 138/211] Add OpenTelemetry layer as optional (#898) Signed-off-by: David Calavera --- examples/opentelemetry-tracing/Cargo.toml | 36 +++++-------------- examples/opentelemetry-tracing/src/main.rs | 5 ++- lambda-runtime/Cargo.toml | 2 ++ lambda-runtime/src/layers/mod.rs | 5 +++ .../src/layers/otel.rs | 12 +++++-- lambda-runtime/src/layers/trace.rs | 2 ++ 6 files changed, 29 insertions(+), 33 deletions(-) rename examples/opentelemetry-tracing/src/lib.rs => lambda-runtime/src/layers/otel.rs (88%) diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index 27a778b5..c80b997d 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -4,33 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -# Library dependencies -lambda_runtime = { path = "../../lambda-runtime" } -pin-project = "1" +lambda_runtime = { path = "../../lambda-runtime", features = ["opentelemetry"] } opentelemetry-semantic-conventions = "0.14" +opentelemetry = "0.22" +opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.3", features = ["trace"] } +pin-project = "1" +serde_json = "1.0" +tokio = "1" tower = "0.4" tracing = "0.1" - -# Binary dependencies -opentelemetry = { version = "0.22", optional = true } -opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"], optional = true } -opentelemetry-stdout = { version = "0.3", features = ["trace"], optional = true } -serde_json = { version = "1.0", optional = true } -tokio = { version = "1", optional = true } -tracing-opentelemetry = { version = "0.23", optional = true } -tracing-subscriber = { version = "0.3", optional = true } - -[features] -build-binary = [ - "opentelemetry", - "opentelemetry_sdk", - "opentelemetry-stdout", - "serde_json", - "tokio", - "tracing-opentelemetry", - "tracing-subscriber", -] - -[[bin]] -name = "opentelemetry-tracing" -required-features = ["build-binary"] +tracing-opentelemetry = "0.23" +tracing-subscriber = "0.3" diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 68038366..4020d87c 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,7 +1,6 @@ -use lambda_runtime::{LambdaEvent, Runtime}; +use lambda_runtime::{layers::OpenTelemetryLayer as OtelLayer, LambdaEvent, Runtime}; use opentelemetry::trace::TracerProvider; use opentelemetry_sdk::{runtime, trace}; -use opentelemetry_tracing::OpenTelemetryLayer; use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; @@ -25,7 +24,7 @@ async fn main() -> Result<(), BoxError> { .init(); // Initialize the Lambda runtime and add OpenTelemetry tracing - let runtime = Runtime::new(service_fn(echo)).layer(OpenTelemetryLayer::new(|| { + let runtime = Runtime::new(service_fn(echo)).layer(OtelLayer::new(|| { // Make sure that the trace is exported before the Lambda runtime is frozen tracer_provider.force_flush(); })); diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 652381eb..58ecc6b6 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -16,6 +16,7 @@ readme = "../README.md" [features] default = ["tracing"] tracing = ["lambda_runtime_api_client/tracing"] +opentelemetry = ["opentelemetry-semantic-conventions"] [dependencies] async-stream = "0.3" @@ -34,6 +35,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +opentelemetry-semantic-conventions = { version = "0.14", optional = true } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 27ce0d68..55fdccd3 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -10,3 +10,8 @@ pub(crate) use api_client::RuntimeApiClientService; pub(crate) use api_response::RuntimeApiResponseService; pub(crate) use panic::CatchPanicService; pub use trace::TracingLayer; + +#[cfg(feature = "opentelemetry")] +mod otel; +#[cfg(feature = "opentelemetry")] +pub use otel::OpenTelemetryLayer; diff --git a/examples/opentelemetry-tracing/src/lib.rs b/lambda-runtime/src/layers/otel.rs similarity index 88% rename from examples/opentelemetry-tracing/src/lib.rs rename to lambda-runtime/src/layers/otel.rs index 82f12a16..1a483a39 100644 --- a/examples/opentelemetry-tracing/src/lib.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::pin::Pin; use std::task; -use lambda_runtime::LambdaInvocation; +use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; @@ -19,6 +19,7 @@ impl OpenTelemetryLayer where F: Fn() + Clone, { + /// Create a new [OpenTelemetryLayer] with the provided flush function. pub fn new(flush_fn: F) -> Self { Self { flush_fn } } @@ -71,9 +72,14 @@ where // After the first execution, we can set 'coldstart' to false self.coldstart = false; - let fut = self.inner.call(req).instrument(span); + let future = { + // Enter the span before calling the inner service + // to ensure that it's assigned as parent of the inner spans. + let _guard = span.enter(); + self.inner.call(req) + }; OpenTelemetryFuture { - future: Some(fut), + future: Some(future.instrument(span)), flush_fn: self.flush_fn.clone(), } } diff --git a/lambda-runtime/src/layers/trace.rs b/lambda-runtime/src/layers/trace.rs index 35c74c12..e93927b1 100644 --- a/lambda-runtime/src/layers/trace.rs +++ b/lambda-runtime/src/layers/trace.rs @@ -44,6 +44,8 @@ where fn call(&mut self, req: LambdaInvocation) -> Self::Future { let span = request_span(&req.context); let future = { + // Enter the span before calling the inner service + // to ensure that it's assigned as parent of the inner spans. let _guard = span.enter(); self.inner.call(req) }; From 3a049fe60222371dea2cef0519216319f6da8848 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:42:26 -0700 Subject: [PATCH 139/211] Organize imports (#899) Group imports by crate. Add instructions to the PR template about clippy and fmt. Remove PR comments because they don't work on forks. Signed-off-by: David Calavera --- .github/PULL_REQUEST_TEMPLATE.md | 10 ++-- .github/workflows/format.yml | 50 +++++-------------- .rustfmt.toml | 14 ++++-- .../src/custom_serde/codebuild_time.rs | 2 +- .../src/custom_serde/float_unix_epoch.rs | 3 +- lambda-events/src/custom_serde/headers.rs | 9 ++-- lambda-events/src/custom_serde/http_method.rs | 6 ++- lambda-events/src/custom_serde/mod.rs | 6 ++- lambda-events/src/encodings/http.rs | 6 ++- lambda-events/src/encodings/mod.rs | 2 +- lambda-events/src/encodings/time.rs | 2 +- lambda-events/src/event/alb/mod.rs | 8 +-- lambda-events/src/event/apigw/mod.rs | 12 +++-- lambda-events/src/event/appsync/mod.rs | 3 +- lambda-events/src/event/autoscaling/mod.rs | 3 +- lambda-events/src/event/cloudformation/mod.rs | 3 +- .../src/event/cloudformation/provider.rs | 3 +- .../src/event/cloudwatch_events/mod.rs | 3 +- lambda-events/src/event/codebuild/mod.rs | 9 ++-- lambda-events/src/event/cognito/mod.rs | 3 +- lambda-events/src/event/dynamodb/mod.rs | 8 +-- lambda-events/src/event/eventbridge/mod.rs | 3 +- lambda-events/src/event/iot/mod.rs | 4 +- lambda-events/src/event/kinesis/event.rs | 6 ++- lambda-events/src/event/rabbitmq/mod.rs | 3 +- lambda-events/src/event/s3/object_lambda.rs | 3 +- lambda-events/src/event/sns/mod.rs | 3 +- lambda-events/src/event/sqs/mod.rs | 6 +-- lambda-extension/src/extension.rs | 12 +++-- lambda-http/src/request.rs | 7 +-- lambda-http/src/response.rs | 16 +++--- lambda-http/src/streaming.rs | 13 +++-- lambda-runtime-api-client/src/body/channel.rs | 14 +++--- lambda-runtime-api-client/src/body/mod.rs | 6 ++- lambda-runtime-api-client/src/body/watch.rs | 10 ++-- lambda-runtime-api-client/src/lib.rs | 5 +- lambda-runtime/src/layers/api_client.rs | 5 +- lambda-runtime/src/layers/api_response.rs | 9 +--- lambda-runtime/src/layers/otel.rs | 7 +-- lambda-runtime/src/layers/panic.rs | 11 ++-- lambda-runtime/src/requests.rs | 7 +-- lambda-runtime/src/runtime.rs | 21 ++++---- 42 files changed, 155 insertions(+), 181 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 31b151e5..f3010c68 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,10 @@ -*Issue #, if available:* +📬 *Issue #, if available:* -*Description of changes:* +✍️ *Description of changes:* -By submitting this pull request +🔏 *By submitting this pull request* -- [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. +- [ ] I confirm that I've ran `cargo +nightly fmt`. +- [ ] I confirm that I've ran `cargo clippy --fix`. - [ ] I confirm that I've made a best effort attempt to update all relevant documentation. +- [ ] I confirm that my contribution is made under the terms of the Apache 2.0 license. diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index f18d1f83..10f8c75f 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -3,53 +3,29 @@ name: Formatting and Linting on: [push, pull_request] jobs: - check: + fmt: + name: Cargo fmt runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt - uses: Swatinem/rust-cache@v2 - - name: Run fmt check id: cargoFmt shell: bash - run: cargo fmt --all -- --check - - name: Notify fmt check - if: failure() && steps.cargoFmt.outcome == 'failure' - uses: actions/github-script@v6 - with: - script: | - const message = `👋 It looks like your code is not formatted like we expect. - - Please run \`cargo fmt\` and push the code again.`; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }); - core.setFailed('It looks like there are formatting errors'); - + run: cargo +nightly fmt --all -- --check + clippy: + name: Cargo clippy + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 - name: Run clippy check id: cargoClippy shell: bash run: cargo clippy --workspace --all-features -- -D warnings - - name: Notify fmt check - if: failure() && steps.cargoClippy.outcome == 'failure' - uses: actions/github-script@v6 - with: - script: | - const message = `👋 It looks like your code has some linting issues. - - Please run \`cargo clippy --fix\` and push the code again.`; - - await github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: message, - }); - core.setFailed('It looks like there are linting errors'); \ No newline at end of file diff --git a/.rustfmt.toml b/.rustfmt.toml index d8e9ca33..d2f48233 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,6 +1,14 @@ edition = "2021" -# imports_granularity is unstable -# # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports -# imports_granularity = "Crate" + # https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#max_width max_width = 120 + +#https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#reorder_imports +reorder_imports = true + +#https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#unstable_features +unstable_features = true + +# imports_granularity is unstable +# https://github.com/rust-lang/rustfmt/blob/master/Configurations.md#merge_imports +imports_granularity = "Crate" diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index bd132b23..07bd0a5c 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, NaiveDateTime, Utc}; -use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError, Visitor}, + ser::Serializer, Deserialize, }; use std::fmt; diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 82fd51df..437e2b1c 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -1,8 +1,7 @@ use serde::{de, ser}; use std::fmt; -use chrono::offset::TimeZone; -use chrono::{DateTime, LocalResult, Utc}; +use chrono::{offset::TimeZone, DateTime, LocalResult, Utc}; enum SerdeError { NonExistent { timestamp: V }, diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 2ef3050e..44884649 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -1,7 +1,8 @@ -use http::header::HeaderName; -use http::{HeaderMap, HeaderValue}; -use serde::de::{self, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor}; -use serde::ser::{Error as SerError, SerializeMap, Serializer}; +use http::{header::HeaderName, HeaderMap, HeaderValue}; +use serde::{ + de::{self, Deserializer, Error as DeError, MapAccess, Unexpected, Visitor}, + ser::{Error as SerError, SerializeMap, Serializer}, +}; use std::{borrow::Cow, fmt}; /// Serialize a http::HeaderMap into a serde str => Vec map diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 63a98eb4..42a94dd7 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -1,6 +1,8 @@ use http::Method; -use serde::de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}; -use serde::ser::Serializer; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}, + ser::Serializer, +}; use std::fmt; pub fn serialize(method: &Method, ser: S) -> Result { diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 030cb5b3..729dee3d 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -1,6 +1,8 @@ use base64::Engine; -use serde::de::{Deserialize, Deserializer, Error as DeError}; -use serde::ser::Serializer; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError}, + ser::Serializer, +}; use std::collections::HashMap; #[cfg(feature = "codebuild")] diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index e013a698..56cce76a 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -1,8 +1,10 @@ use base64::display::Base64Display; use bytes::Bytes; use http_body::{Body as HttpBody, SizeHint}; -use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; -use serde::ser::{Error as SerError, Serialize, Serializer}; +use serde::{ + de::{Deserialize, Deserializer, Error as DeError, Visitor}, + ser::{Error as SerError, Serialize, Serializer}, +}; use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// Representation of http request and response bodies as supported diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index ccc92684..23399664 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{ops::Deref, ops::DerefMut}; +use std::ops::{Deref, DerefMut}; #[cfg(feature = "chrono")] mod time; diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index 6d77b5cf..d4903360 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -1,7 +1,7 @@ use chrono::{DateTime, TimeDelta, TimeZone, Utc}; -use serde::ser::Serializer; use serde::{ de::{Deserializer, Error as DeError}, + ser::Serializer, Deserialize, Serialize, }; use std::ops::{Deref, DerefMut}; diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index a3f96d88..3b4ce9d5 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -1,7 +1,9 @@ -use crate::custom_serde::{ - deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, +use crate::{ + custom_serde::{ + deserialize_headers, deserialize_nullish_boolean, http_method, serialize_headers, serialize_multi_value_headers, + }, + encodings::Body, }; -use crate::encodings::Body; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{Deserialize, Serialize}; diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index d8310755..e0aa1e8c 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -1,9 +1,11 @@ -use crate::custom_serde::{ - deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, - serialize_multi_value_headers, +use crate::{ + custom_serde::{ + deserialize_headers, deserialize_lambda_map, deserialize_nullish_boolean, http_method, serialize_headers, + serialize_multi_value_headers, + }, + encodings::Body, + iam::IamPolicyStatement, }; -use crate::encodings::Body; -use crate::iam::IamPolicyStatement; use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{de::DeserializeOwned, ser::SerializeMap, Deserialize, Deserializer, Serialize, Serializer}; diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 4035f181..63f9ac74 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 707b828a..601e8774 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 8dbf8b5e..44156b97 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -102,8 +102,7 @@ pub enum CloudFormationCustomResourceResponseStatus { mod test { use std::collections::HashMap; - use super::CloudFormationCustomResourceRequest::*; - use super::*; + use super::{CloudFormationCustomResourceRequest::*, *}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index e3ba5bea..a1594eb4 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -90,8 +90,7 @@ where mod test { use std::collections::HashMap; - use super::CloudFormationCustomResourceRequest::*; - use super::*; + use super::{CloudFormationCustomResourceRequest::*, *}; #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 425de865..6384406e 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; pub mod cloudtrail; diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index fdabcb6f..d4970f5a 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -1,8 +1,9 @@ -use crate::custom_serde::{codebuild_time, CodeBuildNumber}; -use crate::encodings::{MinuteDuration, SecondDuration}; +use crate::{ + custom_serde::{codebuild_time, CodeBuildNumber}, + encodings::{MinuteDuration, SecondDuration}, +}; use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; pub type CodeBuildPhaseStatus = String; diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 1b31abcc..a0ebd8d9 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 77cc77d2..2a3d7558 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -1,6 +1,8 @@ -use crate::custom_serde::deserialize_lambda_dynamodb_item; -use crate::time_window::*; -use crate::{custom_serde::float_unix_epoch, streams::DynamoDbBatchItemFailure}; +use crate::{ + custom_serde::{deserialize_lambda_dynamodb_item, float_unix_epoch}, + streams::DynamoDbBatchItemFailure, + time_window::*, +}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use std::fmt; diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 990a853d..7756e0e4 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; /// Parse EventBridge events. diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index cf0ca246..3835b515 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,6 +1,4 @@ -use crate::custom_serde::serialize_headers; -use crate::encodings::Base64Data; -use crate::iam::IamPolicyDocument; +use crate::{custom_serde::serialize_headers, encodings::Base64Data, iam::IamPolicyDocument}; use http::HeaderMap; use serde::{Deserialize, Serialize}; diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 94ebbbf9..fac80e07 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -1,5 +1,7 @@ -use crate::encodings::{Base64Data, SecondTimestamp}; -use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; +use crate::{ + encodings::{Base64Data, SecondTimestamp}, + time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}, +}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 47f3c004..23327276 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -1,5 +1,4 @@ -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index fe52b8a6..2abcc797 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -1,6 +1,5 @@ use http::HeaderMap; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index d72b926a..0fda569d 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,6 +1,5 @@ use chrono::{DateTime, Utc}; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 21359f3a..9dd69f66 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,7 +1,5 @@ -use crate::custom_serde::deserialize_lambda_map; -use crate::encodings::Base64Data; -use serde::de::DeserializeOwned; -use serde::{Deserialize, Serialize}; +use crate::{custom_serde::deserialize_lambda_map, encodings::Base64Data}; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 3800f7ab..d9717243 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -1,14 +1,18 @@ use http::Request; use http_body_util::BodyExt; -use hyper::body::Incoming; -use hyper::server::conn::http1; -use hyper::service::service_fn; +use hyper::{body::Incoming, server::conn::http1, service::service_fn}; use hyper_util::rt::tokio::TokioIo; use lambda_runtime_api_client::Client; use serde::Deserialize; use std::{ - convert::Infallible, fmt, future::ready, future::Future, net::SocketAddr, path::PathBuf, pin::Pin, sync::Arc, + convert::Infallible, + fmt, + future::{ready, Future}, + net::SocketAddr, + path::PathBuf, + pin::Pin, + sync::Arc, }; use tokio::{net::TcpListener, sync::Mutex}; use tokio_stream::StreamExt; diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 9476da3b..afe233eb 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -26,15 +26,12 @@ use aws_lambda_events::apigw::{ApiGatewayV2httpRequest, ApiGatewayV2httpRequestC #[cfg(feature = "apigw_websockets")] use aws_lambda_events::apigw::{ApiGatewayWebsocketProxyRequest, ApiGatewayWebsocketProxyRequestContext}; use aws_lambda_events::{encodings::Body, query_map::QueryMap}; -use http::header::HeaderName; -use http::{HeaderMap, HeaderValue}; +use http::{header::HeaderName, HeaderMap, HeaderValue}; use serde::{Deserialize, Serialize}; use serde_json::error::Error as JsonError; -use std::future::Future; -use std::pin::Pin; -use std::{env, io::Read}; +use std::{env, future::Future, io::Read, pin::Pin}; use url::Url; /// Internal representation of an Lambda http event from diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 6a31cb79..e8528fdf 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -9,16 +9,20 @@ use aws_lambda_events::apigw::ApiGatewayProxyResponse; use aws_lambda_events::apigw::ApiGatewayV2httpResponse; use aws_lambda_events::encodings::Body; use encoding_rs::Encoding; -use http::header::CONTENT_ENCODING; -use http::HeaderMap; -use http::{header::CONTENT_TYPE, Response, StatusCode}; +use http::{ + header::{CONTENT_ENCODING, CONTENT_TYPE}, + HeaderMap, Response, StatusCode, +}; use http_body::Body as HttpBody; use http_body_util::BodyExt; use mime::{Mime, CHARSET}; use serde::Serialize; -use std::borrow::Cow; -use std::future::ready; -use std::{fmt, future::Future, pin::Pin}; +use std::{ + borrow::Cow, + fmt, + future::{ready, Future}, + pin::Pin, +}; const X_LAMBDA_HTTP_CONTENT_ENCODING: &str = "x-lambda-http-content-encoding"; diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index df569129..ad3471d3 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,15 +1,14 @@ -use crate::http::header::SET_COOKIE; -use crate::tower::ServiceBuilder; -use crate::Request; -use crate::{request::LambdaRequest, RequestExt}; +use crate::{http::header::SET_COOKIE, request::LambdaRequest, tower::ServiceBuilder, Request, RequestExt}; use bytes::Bytes; pub use http::{self, Response}; use http_body::Body; use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::fmt::Debug; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + fmt::Debug, + pin::Pin, + task::{Context, Poll}, +}; use tokio_stream::Stream; /// Starts the Lambda Rust runtime and stream response back [Configure Lambda diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index f5fcb77b..27574655 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -1,19 +1,17 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. //! https://github.com/hyperium/hyper/blob/master/LICENSE -use std::pin::Pin; -use std::task::Context; -use std::task::Poll; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use crate::body::{sender, watch}; use bytes::Bytes; -use futures_channel::mpsc; -use futures_channel::oneshot; +use futures_channel::{mpsc, oneshot}; use futures_util::{stream::FusedStream, Future, Stream}; use http::HeaderMap; -use http_body::Body; -use http_body::Frame; -use http_body::SizeHint; +use http_body::{Body, Frame, SizeHint}; pub use sender::Sender; #[derive(Clone, Copy, PartialEq, Eq)] diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs index 7e2d597c..46735682 100644 --- a/lambda-runtime-api-client/src/body/mod.rs +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -6,8 +6,10 @@ use bytes::Bytes; use futures_util::stream::Stream; use http_body::{Body as _, Frame}; use http_body_util::{BodyExt, Collected}; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use self::channel::Sender; diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs index ac0bd4ee..a5f8ae41 100644 --- a/lambda-runtime-api-client/src/body/watch.rs +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -8,11 +8,13 @@ //! - The value `0` is reserved for closed. use futures_util::task::AtomicWaker; -use std::sync::{ - atomic::{AtomicUsize, Ordering}, - Arc, +use std::{ + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, + task, }; -use std::task; type Value = usize; diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 4315dfd7..00c00e4c 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -5,7 +5,10 @@ //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. use futures_util::{future::BoxFuture, FutureExt, TryFutureExt}; -use http::{uri::PathAndQuery, uri::Scheme, Request, Response, Uri}; +use http::{ + uri::{PathAndQuery, Scheme}, + Request, Response, Uri, +}; use hyper::body::Incoming; use hyper_util::client::legacy::connect::HttpConnector; use std::{convert::TryInto, fmt::Debug, future}; diff --git a/lambda-runtime/src/layers/api_client.rs b/lambda-runtime/src/layers/api_client.rs index b6d9acf8..d44a84f2 100644 --- a/lambda-runtime/src/layers/api_client.rs +++ b/lambda-runtime/src/layers/api_client.rs @@ -3,10 +3,7 @@ use futures::{future::BoxFuture, ready, FutureExt, TryFutureExt}; use hyper::body::Incoming; use lambda_runtime_api_client::{body::Body, BoxError, Client}; use pin_project::pin_project; -use std::future::Future; -use std::pin::Pin; -use std::sync::Arc; -use std::task; +use std::{future::Future, pin::Pin, sync::Arc, task}; use tower::Service; use tracing::error; diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 55942d0b..5994012c 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -4,16 +4,11 @@ use crate::{ runtime::LambdaInvocation, Diagnostic, EventErrorRequest, IntoFunctionResponse, LambdaEvent, }; -use futures::ready; -use futures::Stream; +use futures::{ready, Stream}; use lambda_runtime_api_client::{body::Body, BoxError}; use pin_project::pin_project; use serde::{Deserialize, Serialize}; -use std::fmt::Debug; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task; +use std::{fmt::Debug, future::Future, marker::PhantomData, pin::Pin, task}; use tower::Service; use tracing::{error, trace}; diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index 1a483a39..bcba399a 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,13 +1,10 @@ -use std::future::Future; -use std::pin::Pin; -use std::task; +use std::{future::Future, pin::Pin, task}; use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; -use tracing::instrument::Instrumented; -use tracing::Instrument; +use tracing::{instrument::Instrumented, Instrument}; /// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts /// a function to flush OpenTelemetry after the end of the invocation. diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 6600b743..036b747b 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -1,14 +1,9 @@ use crate::{Diagnostic, LambdaEvent}; use futures::{future::CatchUnwind, FutureExt}; use pin_project::pin_project; -use std::any::Any; -use std::borrow::Cow; -use std::fmt::Debug; -use std::future::Future; -use std::marker::PhantomData; -use std::panic::AssertUnwindSafe; -use std::pin::Pin; -use std::task; +use std::{ + any::Any, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task, +}; use tower::Service; use tracing::error; diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index de0835c7..0e2eb59a 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -1,12 +1,9 @@ use crate::{types::ToStreamErrorTrailer, Diagnostic, Error, FunctionResponse, IntoFunctionResponse}; use bytes::Bytes; -use http::header::CONTENT_TYPE; -use http::{Method, Request, Uri}; +use http::{header::CONTENT_TYPE, Method, Request, Uri}; use lambda_runtime_api_client::{body::Body, build_request}; use serde::Serialize; -use std::fmt::Debug; -use std::marker::PhantomData; -use std::str::FromStr; +use std::{fmt::Debug, marker::PhantomData, str::FromStr}; use tokio_stream::{Stream, StreamExt}; pub(crate) trait IntoRequest { diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 92ba5f47..56c8efad 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -1,18 +1,15 @@ -use crate::layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}; -use crate::requests::{IntoRequest, NextEventRequest}; -use crate::types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}; -use crate::{Config, Context, Diagnostic}; +use crate::{ + layers::{CatchPanicService, RuntimeApiClientService, RuntimeApiResponseService}, + requests::{IntoRequest, NextEventRequest}, + types::{invoke_request_id, IntoFunctionResponse, LambdaEvent}, + Config, Context, Diagnostic, +}; use http_body_util::BodyExt; -use lambda_runtime_api_client::BoxError; -use lambda_runtime_api_client::Client as ApiClient; +use lambda_runtime_api_client::{BoxError, Client as ApiClient}; use serde::{Deserialize, Serialize}; -use std::env; -use std::fmt::Debug; -use std::future::Future; -use std::sync::Arc; +use std::{env, fmt::Debug, future::Future, sync::Arc}; use tokio_stream::{Stream, StreamExt}; -use tower::Layer; -use tower::{Service, ServiceExt}; +use tower::{Layer, Service, ServiceExt}; use tracing::trace; /* ----------------------------------------- INVOCATION ---------------------------------------- */ From f8cc32de3166f277375dbf1203aa3936509c1cf0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 25 Jun 2024 19:42:41 -0700 Subject: [PATCH 140/211] Release runtime 0.12 and extension 0.11 (#900) --- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 706dd4db..c1afd732 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.10.0" +version = "0.11.0" edition = "2021" authors = [ "David Calavera ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 28b0f7e0..1165084b 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.11.4" +version = "0.12.0" authors = [ "David Calavera ", "Harold Sun ", @@ -34,7 +34,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.11.3", path = "../lambda-runtime" } +lambda_runtime = { version = "0.12.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 58ecc6b6..9e56d05b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.11.3" +version = "0.12.0" authors = [ "David Calavera ", "Harold Sun ", From 9b88cea689bc30e829994e16db4c5740d58f20e1 Mon Sep 17 00:00:00 2001 From: Oliver THEBAULT Date: Sun, 30 Jun 2024 20:13:06 +0200 Subject: [PATCH 141/211] feat(otel): allow to configure the faas.trigger attribute of the span (#903) * feat(otel): allow to configure the faas.trigger attribute of the span * fix: update opentelemetry-tracing example * fix: use datasource as default faas.trigger + introduce enum OpenTelemetryFaasTrigger --- examples/opentelemetry-tracing/src/main.rs | 18 +++++--- lambda-runtime/src/layers/mod.rs | 2 +- lambda-runtime/src/layers/otel.rs | 50 ++++++++++++++++++++-- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 4020d87c..85c3791c 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,4 +1,7 @@ -use lambda_runtime::{layers::OpenTelemetryLayer as OtelLayer, LambdaEvent, Runtime}; +use lambda_runtime::{ + layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + LambdaEvent, Runtime, +}; use opentelemetry::trace::TracerProvider; use opentelemetry_sdk::{runtime, trace}; use tower::{service_fn, BoxError}; @@ -24,10 +27,15 @@ async fn main() -> Result<(), BoxError> { .init(); // Initialize the Lambda runtime and add OpenTelemetry tracing - let runtime = Runtime::new(service_fn(echo)).layer(OtelLayer::new(|| { - // Make sure that the trace is exported before the Lambda runtime is frozen - tracer_provider.force_flush(); - })); + let runtime = Runtime::new(service_fn(echo)).layer( + // Create a tracing span for each Lambda invocation + OtelLayer::new(|| { + // Make sure that the trace is exported before the Lambda runtime is frozen + tracer_provider.force_flush(); + }) + // Set the "faas.trigger" attribute of the span to "pubsub" + .with_trigger(OpenTelemetryFaasTrigger::PubSub), + ); runtime.run().await?; Ok(()) } diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 55fdccd3..1f07f199 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -14,4 +14,4 @@ pub use trace::TracingLayer; #[cfg(feature = "opentelemetry")] mod otel; #[cfg(feature = "opentelemetry")] -pub use otel::OpenTelemetryLayer; +pub use otel::{OpenTelemetryFaasTrigger, OpenTelemetryLayer}; diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index bcba399a..e6b7cfff 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,4 +1,4 @@ -use std::{future::Future, pin::Pin, task}; +use std::{fmt::Display, future::Future, pin::Pin, task}; use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; @@ -10,6 +10,7 @@ use tracing::{instrument::Instrumented, Instrument}; /// a function to flush OpenTelemetry after the end of the invocation. pub struct OpenTelemetryLayer { flush_fn: F, + otel_attribute_trigger: OpenTelemetryFaasTrigger, } impl OpenTelemetryLayer @@ -18,7 +19,18 @@ where { /// Create a new [OpenTelemetryLayer] with the provided flush function. pub fn new(flush_fn: F) -> Self { - Self { flush_fn } + Self { + flush_fn, + otel_attribute_trigger: Default::default(), + } + } + + /// Configure the `faas.trigger` attribute of the OpenTelemetry span. + pub fn with_trigger(self, trigger: OpenTelemetryFaasTrigger) -> Self { + Self { + otel_attribute_trigger: trigger, + ..self + } } } @@ -33,6 +45,7 @@ where inner, flush_fn: self.flush_fn.clone(), coldstart: true, + otel_attribute_trigger: self.otel_attribute_trigger.to_string(), } } } @@ -42,6 +55,7 @@ pub struct OpenTelemetryService { inner: S, flush_fn: F, coldstart: bool, + otel_attribute_trigger: String, } impl Service for OpenTelemetryService @@ -61,7 +75,7 @@ where let span = tracing::info_span!( "Lambda function invocation", "otel.name" = req.context.env_config.function_name, - { traceconv::FAAS_TRIGGER } = "http", + { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, { traceconv::FAAS_COLDSTART } = self.coldstart ); @@ -114,3 +128,33 @@ where task::Poll::Ready(ready) } } + +/// Represent the possible values for the OpenTelemetry `faas.trigger` attribute. +/// See https://opentelemetry.io/docs/specs/semconv/attributes-registry/faas/ for more details. +#[derive(Default, Clone, Copy)] +#[non_exhaustive] +pub enum OpenTelemetryFaasTrigger { + /// A response to some data source operation such as a database or filesystem read/write + #[default] + Datasource, + /// To provide an answer to an inbound HTTP request + Http, + /// A function is set to be executed when messages are sent to a messaging system + PubSub, + /// A function is scheduled to be executed regularly + Timer, + /// If none of the others apply + Other, +} + +impl Display for OpenTelemetryFaasTrigger { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + OpenTelemetryFaasTrigger::Datasource => write!(f, "datasource"), + OpenTelemetryFaasTrigger::Http => write!(f, "http"), + OpenTelemetryFaasTrigger::PubSub => write!(f, "pubsub"), + OpenTelemetryFaasTrigger::Timer => write!(f, "timer"), + OpenTelemetryFaasTrigger::Other => write!(f, "other"), + } + } +} From c4594f797c9c3a133845298eaa0a2f896ebe4220 Mon Sep 17 00:00:00 2001 From: Taiki Ono Date: Tue, 2 Jul 2024 00:14:34 +0900 Subject: [PATCH 142/211] doc: Add an example for using the anyhow crate (#904) * Add "error handling with anyhow" example Signed-off-by: Taiki Ono * More doc for users who use external error types Signed-off-by: Taiki Ono --------- Signed-off-by: Taiki Ono --- examples/basic-error-anyhow/.gitignore | 1 + examples/basic-error-anyhow/Cargo.toml | 10 ++++++++++ examples/basic-error-anyhow/README.md | 13 +++++++++++++ examples/basic-error-anyhow/src/main.rs | 21 +++++++++++++++++++++ lambda-runtime/src/diagnostic.rs | 3 +++ 5 files changed, 48 insertions(+) create mode 100644 examples/basic-error-anyhow/.gitignore create mode 100644 examples/basic-error-anyhow/Cargo.toml create mode 100644 examples/basic-error-anyhow/README.md create mode 100644 examples/basic-error-anyhow/src/main.rs diff --git a/examples/basic-error-anyhow/.gitignore b/examples/basic-error-anyhow/.gitignore new file mode 100644 index 00000000..ea8c4bf7 --- /dev/null +++ b/examples/basic-error-anyhow/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/basic-error-anyhow/Cargo.toml b/examples/basic-error-anyhow/Cargo.toml new file mode 100644 index 00000000..a0ff62db --- /dev/null +++ b/examples/basic-error-anyhow/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "basic-error-anyhow" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +lambda_runtime = { path = "../../lambda-runtime" } +serde = "1" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/README.md b/examples/basic-error-anyhow/README.md new file mode 100644 index 00000000..b659c283 --- /dev/null +++ b/examples/basic-error-anyhow/README.md @@ -0,0 +1,13 @@ +# AWS Lambda Function Error Handling With `anyhow` Crate Example + +This example shows how to use external error types like `anyhow::Error`. + +## Build & Deploy + +1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/basic-error-anyhow/src/main.rs b/examples/basic-error-anyhow/src/main.rs new file mode 100644 index 00000000..cbca84fd --- /dev/null +++ b/examples/basic-error-anyhow/src/main.rs @@ -0,0 +1,21 @@ +use anyhow::bail; +use lambda_runtime::{service_fn, Error, LambdaEvent}; +use serde::Deserialize; + +#[derive(Deserialize)] +struct Request {} + +/// Return anyhow::Result in the main body for the Lambda function. +async fn function_handler(_event: LambdaEvent) -> anyhow::Result<()> { + bail!("This is an error message"); +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + lambda_runtime::run(service_fn(|event: LambdaEvent| async move { + function_handler(event) + .await + .map_err(Into::>::into) + })) + .await +} diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index bc9ba623..9a7230a7 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -7,6 +7,9 @@ use crate::{deserializer::DeserializeError, Error}; /// /// `Diagnostic` is automatically derived for some common types, /// like boxed types that implement [`Error`][std::error::Error]. +/// If you use an error type which comes from a external crate like anyhow, +/// you need convert it to common types like `Box`. +/// See the examples for more details. /// /// [`error_type`][`Diagnostic::error_type`] is derived from the type name of /// the original error with [`std::any::type_name`] as a fallback, which may From 4ee10b0906728d34926b1eba2e90702e782472ec Mon Sep 17 00:00:00 2001 From: mx Date: Thu, 4 Jul 2024 03:07:58 +1200 Subject: [PATCH 143/211] Improved payload Deser error messages (#905) * made error messages more informative * added a suggestion to log at TRACE level to README --- README.md | 4 +++- lambda-runtime/src/layers/api_response.rs | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0a19e467..44ec1983 100644 --- a/README.md +++ b/README.md @@ -306,7 +306,9 @@ async fn main() -> Result<(), Error> { } ``` -The subscriber uses `RUST_LOG` as the environment variable to determine the log level for your function. It also uses [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) if they're configured for your function. By default, the log level to emit events is `INFO`. +The subscriber uses `RUST_LOG` environment variable to determine the log level for your function. It also uses [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/), if configured. + +By default, the log level to emit events is `INFO`. Log at `TRACE` level for more detail, including a dump of the raw payload. ## AWS event objects diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 5994012c..7c963f68 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -98,7 +98,7 @@ where Ok(()) }; if let Err(err) = trace_fn() { - error!(error = ?err, "failed to parse raw JSON event received from Lambda"); + error!(error = ?err, "Failed to parse raw JSON event received from Lambda. The handler will not be called. Log at TRACE level to see the payload."); return RuntimeApiResponseFuture::Ready(Some(Err(err))); }; @@ -124,7 +124,7 @@ fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result> + Debug, { - error!(error = ?err, "building error response for Lambda Runtime API"); + error!(error = ?err, "Request payload deserialization into LambdaEvent failed. The handler will not be called. Log at TRACE level to see the payload."); EventErrorRequest::new(request_id, err).into_req() } From 4b5ffc8c435809ed471a530a6b613bd81f8fa2ed Mon Sep 17 00:00:00 2001 From: Kevin Chan Date: Tue, 9 Jul 2024 10:33:21 +1000 Subject: [PATCH 144/211] added RDS IAM Auth example (#908) * added RDS IAM Auth example * restructuring project for testing * removing CDK temp files * removing package-lock.json --- examples/lambda-rds-iam-auth/.gitignore | 10 + examples/lambda-rds-iam-auth/Cargo.toml | 14 + examples/lambda-rds-iam-auth/cdk/.gitignore | 134 + examples/lambda-rds-iam-auth/cdk/README.md | 8 + examples/lambda-rds-iam-auth/cdk/app.ts | 105 + examples/lambda-rds-iam-auth/cdk/cdk.json | 21 + examples/lambda-rds-iam-auth/cdk/package.json | 13 + .../lambda-rds-iam-auth/cdk/tsconfig.json | 31 + .../lambda-rds-iam-auth/src/global-bundle.pem | 3028 +++++++++++++++++ examples/lambda-rds-iam-auth/src/main.rs | 109 + 10 files changed, 3473 insertions(+) create mode 100644 examples/lambda-rds-iam-auth/.gitignore create mode 100644 examples/lambda-rds-iam-auth/Cargo.toml create mode 100644 examples/lambda-rds-iam-auth/cdk/.gitignore create mode 100644 examples/lambda-rds-iam-auth/cdk/README.md create mode 100644 examples/lambda-rds-iam-auth/cdk/app.ts create mode 100644 examples/lambda-rds-iam-auth/cdk/cdk.json create mode 100644 examples/lambda-rds-iam-auth/cdk/package.json create mode 100644 examples/lambda-rds-iam-auth/cdk/tsconfig.json create mode 100644 examples/lambda-rds-iam-auth/src/global-bundle.pem create mode 100644 examples/lambda-rds-iam-auth/src/main.rs diff --git a/examples/lambda-rds-iam-auth/.gitignore b/examples/lambda-rds-iam-auth/.gitignore new file mode 100644 index 00000000..fbbbb6eb --- /dev/null +++ b/examples/lambda-rds-iam-auth/.gitignore @@ -0,0 +1,10 @@ +# Rust +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk diff --git a/examples/lambda-rds-iam-auth/Cargo.toml b/examples/lambda-rds-iam-auth/Cargo.toml new file mode 100644 index 00000000..a1e212ae --- /dev/null +++ b/examples/lambda-rds-iam-auth/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "rds-iam-rust-lambda" +version = "0.1.0" +edition = "2021" + +[dependencies] +lambda_runtime = { path = "../../lambda-runtime" } +serde_json = "1.0.120" +aws-config = "1.0.1" +aws-credential-types = "1.0.1" +aws-sigv4 = "1.0.1" +url = "2.5.0" +tokio = { version = "1.25.0", features = ["full"] } +sqlx = { version = "0.7.4", features = ["tls-rustls", "postgres", "runtime-tokio"] } diff --git a/examples/lambda-rds-iam-auth/cdk/.gitignore b/examples/lambda-rds-iam-auth/cdk/.gitignore new file mode 100644 index 00000000..b2e6f363 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/.gitignore @@ -0,0 +1,134 @@ +# CDK +node_modules +cdk.json + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* diff --git a/examples/lambda-rds-iam-auth/cdk/README.md b/examples/lambda-rds-iam-auth/cdk/README.md new file mode 100644 index 00000000..4378fb42 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/README.md @@ -0,0 +1,8 @@ +# AWS Lambda Function that uses RDS's IAM Authnetication +This example shows how to build and deploy Rust Lambda Function and an RDS instance using AWS CDK and + +Build & Deploy +1. `npm install` +1. `npx cdk deploy` +1. Using the dev instance or using a local Postgres client: connect into the RDS instance as root and create the required Users with permissions `CREATE USER lambda; GRANT rds_iam TO lambda;` +1. Go to the Lambda Function in the AWS console and invoke the lambda function \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/cdk/app.ts b/examples/lambda-rds-iam-auth/cdk/app.ts new file mode 100644 index 00000000..127afad9 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/app.ts @@ -0,0 +1,105 @@ +import { join } from 'path'; +import * as cdk from 'aws-cdk-lib'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import { RustFunction } from '@cdklabs/aws-lambda-rust' + +class LambdaRDSStack extends cdk.Stack { + constructor(scope: cdk.App, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create a VPC + const vpc = new ec2.Vpc(this, 'VPC'); + + // Admin DB user + const DB_ADMIN_USERNAME = 'root'; + const DB_USERNAME = 'lambda'; + + // Lambda DB user + const DB_NAME = 'foo'; + + // Create an RDS instance + const db = new rds.DatabaseInstance(this, 'Postgres', { + engine: rds.DatabaseInstanceEngine.POSTGRES, + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + credentials: rds.Credentials.fromGeneratedSecret(DB_ADMIN_USERNAME), + iamAuthentication: true, + publiclyAccessible: true, + databaseName: DB_NAME, + deleteAutomatedBackups: true, + removalPolicy: cdk.RemovalPolicy.DESTROY + }) + + db.connections.allowFromAnyIpv4(ec2.Port.allTcp()) + + // RDS SSL Cert Lambda Layer alternative to loading the certificates at compile time + /* + const certLayer = new lambda.LayerVersion(this, 'CertLayer', { + description: 'SSL Certificate Layer', + code: lambda.Code.fromAsset('certs'), + compatibleArchitectures: [lambda.Architecture.X86_64, lambda.Architecture.ARM_64] + }); + */ + + const lambdaSG = new ec2.SecurityGroup(this, 'LambdaSG', { + securityGroupName: 'LambdaSG', + allowAllOutbound: true, + vpc: vpc, + }) + // create a rust lambda function + const rustLambdaFunction = new RustFunction(this, "lambda", { + entry: join(__dirname, '..', 'lambda'), + vpc: vpc, + securityGroups: [lambdaSG], + environment: { + DB_HOSTNAME: db.dbInstanceEndpointAddress, + DB_PORT: db.dbInstanceEndpointPort, + DB_NAME: DB_NAME, + DB_USERNAME: DB_USERNAME, + }, + bundling: { + forceDockerBundling: true, + }, + runtime: lambda.Runtime.PROVIDED_AL2023, + timeout: cdk.Duration.seconds(60), + }); + + // MySQL + /* + CREATE USER 'lambda' IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS'; + GRANT ALL PRIVILEGES ON foo.* TO 'lambda'; + ALTER USER 'lambda' REQUIRE SSL; + */ + + // Postgres + /* + CREATE USER db_userx; + GRANT rds_iam TO db_userx; + */ + db.grantConnect(rustLambdaFunction, DB_USERNAME); + db.connections.allowDefaultPortFrom(rustLambdaFunction); + + /* + Dev Instance for initialising the datbase with the above commands + */ + const devInstance = new ec2.Instance(this, 'dev', { + vpc, + vpcSubnets: vpc.selectSubnets({ subnetType: ec2.SubnetType.PUBLIC }), + machineImage: ec2.MachineImage.latestAmazonLinux2023(), + instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MEDIUM) + }) + db.grantConnect(devInstance, DB_ADMIN_USERNAME); + db.grantConnect(devInstance, DB_USERNAME); + db.connections.allowDefaultPortFrom(devInstance); + + // Output the Lambda function ARN + new cdk.CfnOutput(this, 'LambdaFunctionConsole', { + value: `https://${this.region}.console.aws.amazon.com/lambda/home?region=${this.region}#/functions/${rustLambdaFunction.functionName}?tab=testing` + }); + } +} + +const app = new cdk.App(); +new LambdaRDSStack(app, 'LambdaRDSStack'); diff --git a/examples/lambda-rds-iam-auth/cdk/cdk.json b/examples/lambda-rds-iam-auth/cdk/cdk.json new file mode 100644 index 00000000..8d92ca81 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/cdk.json @@ -0,0 +1,21 @@ +{ + "app": "npx ts-node --prefer-ts-exts app.ts", + "watch": { + "include": [ + "**.js", + "**.rs", + "**.ts" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + } +} \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/cdk/package.json b/examples/lambda-rds-iam-auth/cdk/package.json new file mode 100644 index 00000000..ebaf98c5 --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/package.json @@ -0,0 +1,13 @@ +{ + "dependencies": { + "@cdklabs/aws-lambda-rust": "0.0.4", + "aws-cdk-lib": "^2.147.0", + "path": "^0.12.7", + "prettier": "^3.3.2", + "rust.aws-cdk-lambda": "^1.2.1", + "ts-node": "^10.9.2" + }, + "devDependencies": { + "@types/node": "^20.14.10" + } +} diff --git a/examples/lambda-rds-iam-auth/cdk/tsconfig.json b/examples/lambda-rds-iam-auth/cdk/tsconfig.json new file mode 100644 index 00000000..72067eca --- /dev/null +++ b/examples/lambda-rds-iam-auth/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} \ No newline at end of file diff --git a/examples/lambda-rds-iam-auth/src/global-bundle.pem b/examples/lambda-rds-iam-auth/src/global-bundle.pem new file mode 100644 index 00000000..de68d41a --- /dev/null +++ b/examples/lambda-rds-iam-auth/src/global-bundle.pem @@ -0,0 +1,3028 @@ +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAM2ZN/+nPi27MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDI4MTgwNTU4WhcNMjQxMDI2MTgwNTU4WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgYWYtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwR2351uPMZaJk2gMGT+1sk8HE9MQh2rc +/sCnbxGn2p1c7Oi9aBbd/GiFijeJb2BXvHU+TOq3d3Jjqepq8tapXVt4ojbTJNyC +J5E7r7KjTktKdLxtBE1MK25aY+IRJjtdU6vG3KiPKUT1naO3xs3yt0F76WVuFivd +9OHv2a+KHvPkRUWIxpmAHuMY9SIIMmEZtVE7YZGx5ah0iO4JzItHcbVR0y0PBH55 +arpFBddpIVHCacp1FUPxSEWkOpI7q0AaU4xfX0fe1BV5HZYRKpBOIp1TtZWvJD+X +jGUtL1BEsT5vN5g9MkqdtYrC+3SNpAk4VtpvJrdjraI/hhvfeXNnAwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUEEi/ +WWMcBJsoGXg+EZwkQ0MscZQwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0Ms +cZQwDQYJKoZIhvcNAQELBQADggEBAGDZ5js5Pc/gC58LJrwMPXFhJDBS8QuDm23C +FFUdlqucskwOS3907ErK1ZkmVJCIqFLArHqskFXMAkRZ2PNR7RjWLqBs+0znG5yH +hRKb4DXzhUFQ18UBRcvT6V6zN97HTRsEEaNhM/7k8YLe7P8vfNZ28VIoJIGGgv9D +wQBBvkxQ71oOmAG0AwaGD0ORGUfbYry9Dz4a4IcUsZyRWRMADixgrFv6VuETp26s +/+z+iqNaGWlELBKh3iQCT6Y/1UnkPLO42bxrCSyOvshdkYN58Q2gMTE1SVTqyo8G +Lw8lLAz9bnvUSgHzB3jRrSx6ggF/WRMRYlR++y6LXP4SAsSAaC0= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJAJYM4LxvTZA6MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkxMDMwMjAyMDM2WhcNMjQxMDI4MjAyMDM2WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgZXUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqM921jXCXeqpRNCS9CBPOe5N7gMaEt+D +s5uR3riZbqzRlHGiF1jZihkXfHAIQewDwy+Yz+Oec1aEZCQMhUHxZJPusuX0cJfj +b+UluFqHIijL2TfXJ3D0PVLLoNTQJZ8+GAPECyojAaNuoHbdVqxhOcznMsXIXVFq +yVLKDGvyKkJjai/iSPDrQMXufg3kWt0ISjNLvsG5IFXgP4gttsM8i0yvRd4QcHoo +DjvH7V3cS+CQqW5SnDrGnHToB0RLskE1ET+oNOfeN9PWOxQprMOX/zmJhnJQlTqD +QP7jcf7SddxrKFjuziFiouskJJyNDsMjt1Lf60+oHZhed2ogTeifGwIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUFBAF +cgJe/BBuZiGeZ8STfpkgRYQwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkg +RYQwDQYJKoZIhvcNAQELBQADggEBAKAYUtlvDuX2UpZW9i1QgsjFuy/ErbW0dLHU +e/IcFtju2z6RLZ+uF+5A8Kme7IKG1hgt8s+w9TRVQS/7ukQzoK3TaN6XKXRosjtc +o9Rm4gYWM8bmglzY1TPNaiI4HC7546hSwJhubjN0bXCuj/0sHD6w2DkiGuwKNAef +yTu5vZhPkeNyXLykxkzz7bNp2/PtMBnzIp+WpS7uUDmWyScGPohKMq5PqvL59z+L +ZI3CYeMZrJ5VpXUg3fNNIz/83N3G0sk7wr0ohs/kHTP7xPOYB0zD7Ku4HA0Q9Swf +WX0qr6UQgTPMjfYDLffI7aEId0gxKw1eGYc6Cq5JAZ3ipi/cBFc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEjCCAvqgAwIBAgIJANew34ehz5l8MA0GCSqGSIb3DQEBCwUAMIGVMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEmMCQGA1UEAwwdQW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0Ew +HhcNMTkwNTEwMjE0ODI3WhcNMjQwNTA4MjE0ODI3WjCBlTELMAkGA1UEBhMCVVMx +EDAOBgNVBAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoM +GUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMx +JjAkBgNVBAMMHUFtYXpvbiBSRFMgbWUtc291dGgtMSBSb290IENBMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAp7BYV88MukcY+rq0r79+C8UzkT30fEfT +aPXbx1d6M7uheGN4FMaoYmL+JE1NZPaMRIPTHhFtLSdPccInvenRDIatcXX+jgOk +UA6lnHQ98pwN0pfDUyz/Vph4jBR9LcVkBbe0zdoKKp+HGbMPRU0N2yNrog9gM5O8 +gkU/3O2csJ/OFQNnj4c2NQloGMUpEmedwJMOyQQfcUyt9CvZDfIPNnheUS29jGSw +ERpJe/AENu8Pxyc72jaXQuD+FEi2Ck6lBkSlWYQFhTottAeGvVFNCzKszCntrtqd +rdYUwurYsLTXDHv9nW2hfDUQa0mhXf9gNDOBIVAZugR9NqNRNyYLHQIDAQABo2Mw +YTAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU54cf +DjgwBx4ycBH8+/r8WXdaiqYwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXda +iqYwDQYJKoZIhvcNAQELBQADggEBAIIMTSPx/dR7jlcxggr+O6OyY49Rlap2laKA +eC/XI4ySP3vQkIFlP822U9Kh8a9s46eR0uiwV4AGLabcu0iKYfXjPkIprVCqeXV7 +ny9oDtrbflyj7NcGdZLvuzSwgl9SYTJp7PVCZtZutsPYlbJrBPHwFABvAkMvRtDB +hitIg4AESDGPoCl94sYHpfDfjpUDMSrAMDUyO6DyBdZH5ryRMAs3lGtsmkkNUrso +aTW6R05681Z0mvkRdb+cdXtKOSuDZPoe2wJJIaz3IlNQNSrB5TImMYgmt6iAsFhv +3vfTSTKrZDNTJn4ybG6pq1zWExoXsktZPylJly6R3RBwV6nwqBM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBjCCAu6gAwIBAgIJAMc0ZzaSUK51MA0GCSqGSIb3DQEBCwUAMIGPMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkw +ODIyMTcwODUwWhcNMjQwODIyMTcwODUwWjCBjzELMAkGA1UEBhMCVVMxEDAOBgNV +BAcMB1NlYXR0bGUxEzARBgNVBAgMCldhc2hpbmd0b24xIjAgBgNVBAoMGUFtYXpv +biBXZWIgU2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxIDAeBgNV +BAMMF0FtYXpvbiBSRFMgUm9vdCAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEArXnF/E6/Qh+ku3hQTSKPMhQQlCpoWvnIthzX6MK3p5a0eXKZ +oWIjYcNNG6UwJjp4fUXl6glp53Jobn+tWNX88dNH2n8DVbppSwScVE2LpuL+94vY +0EYE/XxN7svKea8YvlrqkUBKyxLxTjh+U/KrGOaHxz9v0l6ZNlDbuaZw3qIWdD/I +6aNbGeRUVtpM6P+bWIoxVl/caQylQS6CEYUk+CpVyJSkopwJlzXT07tMoDL5WgX9 +O08KVgDNz9qP/IGtAcRduRcNioH3E9v981QO1zt/Gpb2f8NqAjUUCUZzOnij6mx9 +McZ+9cWX88CRzR0vQODWuZscgI08NvM69Fn2SQIDAQABo2MwYTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUc19g2LzLA5j0Kxc0LjZa +pmD/vB8wHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJKoZIhvcN +AQELBQADggEBAHAG7WTmyjzPRIM85rVj+fWHsLIvqpw6DObIjMWokpliCeMINZFV +ynfgBKsf1ExwbvJNzYFXW6dihnguDG9VMPpi2up/ctQTN8tm9nDKOy08uNZoofMc +NUZxKCEkVKZv+IL4oHoeayt8egtv3ujJM6V14AstMQ6SwvwvA93EP/Ug2e4WAXHu +cbI1NAbUgVDqp+DRdfvZkgYKryjTWd/0+1fS8X1bBZVWzl7eirNVnHbSH2ZDpNuY +0SBd8dj5F6ld3t58ydZbrTHze7JJOd8ijySAp4/kiu9UfZWuTPABzDa/DSdz9Dk/ +zPW4CXXvhLmE02TA9/HeCw3KEHIwicNuEfw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEEDCCAvigAwIBAgIJAKFMXyltvuRdMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzElMCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTAe +Fw0xOTA4MTkxNzM4MjZaFw0yNDA4MTkxNzM4MjZaMIGUMQswCQYDVQQGEwJVUzEQ +MA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UECgwZ +QW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEl +MCMGA1UEAwwcQW1hem9uIFJEUyBCZXRhIFJvb3QgMjAxOSBDQTCCASIwDQYJKoZI +hvcNAQEBBQADggEPADCCAQoCggEBAMkZdnIH9ndatGAcFo+DppGJ1HUt4x+zeO+0 +ZZ29m0sfGetVulmTlv2d5b66e+QXZFWpcPQMouSxxYTW08TbrQiZngKr40JNXftA +atvzBqIImD4II0ZX5UEVj2h98qe/ypW5xaDN7fEa5e8FkYB1TEemPaWIbNXqchcL +tV7IJPr3Cd7Z5gZJlmujIVDPpMuSiNaal9/6nT9oqN+JSM1fx5SzrU5ssg1Vp1vv +5Xab64uOg7wCJRB9R2GC9XD04odX6VcxUAGrZo6LR64ZSifupo3l+R5sVOc5i8NH +skdboTzU9H7+oSdqoAyhIU717PcqeDum23DYlPE2nGBWckE+eT8CAwEAAaNjMGEw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFK2hDBWl +sbHzt/EHd0QYOooqcFPhMB8GA1UdIwQYMBaAFK2hDBWlsbHzt/EHd0QYOooqcFPh +MA0GCSqGSIb3DQEBCwUAA4IBAQAO/718k8EnOqJDx6wweUscGTGL/QdKXUzTVRAx +JUsjNUv49mH2HQVEW7oxszfH6cPCaupNAddMhQc4C/af6GHX8HnqfPDk27/yBQI+ +yBBvIanGgxv9c9wBbmcIaCEWJcsLp3HzXSYHmjiqkViXwCpYfkoV3Ns2m8bp+KCO +y9XmcCKRaXkt237qmoxoh2sGmBHk2UlQtOsMC0aUQ4d7teAJG0q6pbyZEiPyKZY1 +XR/UVxMJL0Q4iVpcRS1kaNCMfqS2smbLJeNdsan8pkw1dvPhcaVTb7CvjhJtjztF +YfDzAI5794qMlWxwilKMmUvDlPPOTen8NNHkLwWvyFCH7Doh +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFjCCAv6gAwIBAgIJAMzYZJ+R9NBVMA0GCSqGSIb3DQEBCwUAMIGXMQswCQYD +VQQGEwJVUzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEi +MCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1h +em9uIFJEUzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBD +QTAeFw0xOTA4MjEyMjI5NDlaFw0yNDA4MjEyMjI5NDlaMIGXMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEoMCYGA1UEAwwfQW1hem9uIFJEUyBQcmV2aWV3IFJvb3QgMjAxOSBDQTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM7kkS6vjgKKQTPynC2NjdN5aPPV +O71G0JJS/2ARVBVJd93JLiGovVJilfWYfwZCs4gTRSSjrUD4D4HyqCd6A+eEEtJq +M0DEC7i0dC+9WNTsPszuB206Jy2IUmxZMIKJAA1NHSbIMjB+b6/JhbSUi7nKdbR/ +brj83bF+RoSA+ogrgX7mQbxhmFcoZN9OGaJgYKsKWUt5Wqv627KkGodUK8mDepgD +S3ZfoRQRx3iceETpcmHJvaIge6+vyDX3d9Z22jmvQ4AKv3py2CmU2UwuhOltFDwB +0ddtb39vgwrJxaGfiMRHpEP1DfNLWHAnA69/pgZPwIggidS+iBPUhgucMp8CAwEA +AaNjMGEwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FGnTGpQuQ2H/DZlXMQijZEhjs7TdMB8GA1UdIwQYMBaAFGnTGpQuQ2H/DZlXMQij +ZEhjs7TdMA0GCSqGSIb3DQEBCwUAA4IBAQC3xz1vQvcXAfpcZlngiRWeqU8zQAMQ +LZPCFNv7PVk4pmqX+ZiIRo4f9Zy7TrOVcboCnqmP/b/mNq0gVF4O+88jwXJZD+f8 +/RnABMZcnGU+vK0YmxsAtYU6TIb1uhRFmbF8K80HHbj9vSjBGIQdPCbvmR2zY6VJ +BYM+w9U9hp6H4DVMLKXPc1bFlKA5OBTgUtgkDibWJKFOEPW3UOYwp9uq6pFoN0AO +xMTldqWFsOF3bJIlvOY0c/1EFZXu3Ns6/oCP//Ap9vumldYMUZWmbK+gK33FPOXV +8BQ6jNC29icv7lLDpRPwjibJBXX+peDR5UK4FdYcswWEB1Tix5X8dYu6 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MjgxODA2NTNaFw0yNDEwMjgxODA2NTNaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBhZi1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAvtV1OqmFa8zCVQSKOvPUJERLVFtd4rZmDpImc5rIoeBk7w/P +9lcKUJjO8R/w1a2lJXx3oQ81tiY0Piw6TpT62YWVRMWrOw8+Vxq1dNaDSFp9I8d0 +UHillSSbOk6FOrPDp+R6AwbGFqUDebbN5LFFoDKbhNmH1BVS0a6YNKpGigLRqhka +cClPslWtPqtjbaP3Jbxl26zWzLo7OtZl98dR225pq8aApNBwmtgA7Gh60HK/cX0t +32W94n8D+GKSg6R4MKredVFqRTi9hCCNUu0sxYPoELuM+mHiqB5NPjtm92EzCWs+ ++vgWhMc6GxG+82QSWx1Vj8sgLqtE/vLrWddf5QIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUuLB4gYVJrSKJj/Gz +pqc6yeA+RcAwHwYDVR0jBBgwFoAUEEi/WWMcBJsoGXg+EZwkQ0MscZQwDQYJKoZI +hvcNAQELBQADggEBABauYOZxUhe9/RhzGJ8MsWCz8eKcyDVd4FCnY6Qh+9wcmYNT +LtnD88LACtJKb/b81qYzcB0Em6+zVJ3Z9jznfr6buItE6es9wAoja22Xgv44BTHL +rimbgMwpTt3uEMXDffaS0Ww6YWb3pSE0XYI2ISMWz+xRERRf+QqktSaL39zuiaW5 +tfZMre+YhohRa/F0ZQl3RCd6yFcLx4UoSPqQsUl97WhYzwAxZZfwvLJXOc4ATt3u +VlCUylNDkaZztDJc/yN5XQoK9W5nOt2cLu513MGYKbuarQr8f+gYU8S+qOyuSRSP +NRITzwCRVnsJE+2JmcRInn/NcanB7uOGqTvJ9+c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQTAeFw0xOTEw +MzAyMDIxMzBaFw0yNDEwMzAyMDIxMzBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBldS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAtEyjYcajx6xImJn8Vz1zjdmL4ANPgQXwF7+tF7xccmNAZETb +bzb3I9i5fZlmrRaVznX+9biXVaGxYzIUIR3huQ3Q283KsDYnVuGa3mk690vhvJbB +QIPgKa5mVwJppnuJm78KqaSpi0vxyCPe3h8h6LLFawVyWrYNZ4okli1/U582eef8 +RzJp/Ear3KgHOLIiCdPDF0rjOdCG1MOlDLixVnPn9IYOciqO+VivXBg+jtfc5J+L +AaPm0/Yx4uELt1tkbWkm4BvTU/gBOODnYziITZM0l6Fgwvbwgq5duAtKW+h031lC +37rEvrclqcp4wrsUYcLAWX79ZyKIlRxcAdvEhQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQU7zPyc0azQxnBCe7D +b9KAadH1QSEwHwYDVR0jBBgwFoAUFBAFcgJe/BBuZiGeZ8STfpkgRYQwDQYJKoZI +hvcNAQELBQADggEBAFGaNiYxg7yC/xauXPlaqLCtwbm2dKyK9nIFbF/7be8mk7Q3 +MOA0of1vGHPLVQLr6bJJpD9MAbUcm4cPAwWaxwcNpxOjYOFDaq10PCK4eRAxZWwF +NJRIRmGsl8NEsMNTMCy8X+Kyw5EzH4vWFl5Uf2bGKOeFg0zt43jWQVOX6C+aL3Cd +pRS5MhmYpxMG8irrNOxf4NVFE2zpJOCm3bn0STLhkDcV/ww4zMzObTJhiIb5wSWn +EXKKWhUXuRt7A2y1KJtXpTbSRHQxE++69Go1tWhXtRiULCJtf7wF2Ksm0RR/AdXT +1uR1vKyH5KBJPX3ppYkQDukoHTFR0CpB+G84NLo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZUxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSYwJAYDVQQDDB1BbWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQTAeFw0xOTA1 +MTAyMTU4NDNaFw0yNTA2MDExMjAwMDBaMIGQMQswCQYDVQQGEwJVUzETMBEGA1UE +CAwKV2FzaGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9u +IFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEhMB8GA1UE +AwwYQW1hem9uIFJEUyBtZS1zb3V0aC0xIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAudOYPZH+ihJAo6hNYMB5izPVBe3TYhnZm8+X3IoaaYiKtsp1 +JJhkTT0CEejYIQ58Fh4QrMUyWvU8qsdK3diNyQRoYLbctsBPgxBR1u07eUJDv38/ +C1JlqgHmMnMi4y68Iy7ymv50QgAMuaBqgEBRI1R6Lfbyrb2YvH5txjJyTVMwuCfd +YPAtZVouRz0JxmnfsHyxjE+So56uOKTDuw++Ho4HhZ7Qveej7XB8b+PIPuroknd3 +FQB5RVbXRvt5ZcVD4F2fbEdBniF7FAF4dEiofVCQGQ2nynT7dZdEIPfPdH3n7ZmE +lAOmwHQ6G83OsiHRBLnbp+QZRgOsjkHJxT20bQIDAQABo2YwZDAOBgNVHQ8BAf8E +BAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUOEVDM7VomRH4HVdA +QvIMNq2tXOcwHwYDVR0jBBgwFoAU54cfDjgwBx4ycBH8+/r8WXdaiqYwDQYJKoZI +hvcNAQELBQADggEBAHhvMssj+Th8IpNePU6RH0BiL6o9c437R3Q4IEJeFdYL+nZz +PW/rELDPvLRUNMfKM+KzduLZ+l29HahxefejYPXtvXBlq/E/9czFDD4fWXg+zVou +uDXhyrV4kNmP4S0eqsAP/jQHPOZAMFA4yVwO9hlqmePhyDnszCh9c1PfJSBh49+b +4w7i/L3VBOMt8j3EKYvqz0gVfpeqhJwL4Hey8UbVfJRFJMJzfNHpePqtDRAY7yjV +PYquRaV2ab/E+/7VFkWMM4tazYz/qsYA2jSH+4xDHvYk8LnsbcrF9iuidQmEc5sb +FgcWaSKG4DJjcI5k7AJLWcXyTDt21Ci43LE+I9Q= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICVIYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDQxNzEz +MDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBhcC1zb3V0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDUYOz1hGL42yUCrcsMSOoU8AeD/3KgZ4q7gP+vAz1WnY9K/kim +eWN/2Qqzlo3+mxSFQFyD4MyV3+CnCPnBl9Sh1G/F6kThNiJ7dEWSWBQGAB6HMDbC +BaAsmUc1UIz8sLTL3fO+S9wYhA63Wun0Fbm/Rn2yk/4WnJAaMZcEtYf6e0KNa0LM +p/kN/70/8cD3iz3dDR8zOZFpHoCtf0ek80QqTich0A9n3JLxR6g6tpwoYviVg89e +qCjQ4axxOkWWeusLeTJCcY6CkVyFvDAKvcUl1ytM5AiaUkXblE7zDFXRM4qMMRdt +lPm8d3pFxh0fRYk8bIKnpmtOpz3RIctDrZZxAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBT99wKJftD3jb4sHoHG +i3uGlH6W6TAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAZ17hhr3dII3hUfuHQ1hPWGrpJOX/G9dLzkprEIcCidkmRYl+ +hu1Pe3caRMh/17+qsoEErmnVq5jNY9X1GZL04IZH8YbHc7iRHw3HcWAdhN8633+K +jYEB2LbJ3vluCGnCejq9djDb6alOugdLMJzxOkHDhMZ6/gYbECOot+ph1tQuZXzD +tZ7prRsrcuPBChHlPjmGy8M9z8u+kF196iNSUGC4lM8vLkHM7ycc1/ZOwRq9aaTe +iOghbQQyAEe03MWCyDGtSmDfr0qEk+CHN+6hPiaL8qKt4s+V9P7DeK4iW08ny8Ox +AVS7u0OK/5+jKMAMrKwpYrBydOjTUTHScocyNw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICQ2QwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MDUxODQ2 +MjlaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBzYS1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMMvR+ReRnOzqJzoaPipNTt1Z2VA968jlN1+SYKUrYM3No+Vpz0H +M6Tn0oYB66ByVsXiGc28ulsqX1HbHsxqDPwvQTKvO7SrmDokoAkjJgLocOLUAeld +5AwvUjxGRP6yY90NV7X786MpnYb2Il9DIIaV9HjCmPt+rjy2CZjS0UjPjCKNfB8J +bFjgW6GGscjeyGb/zFwcom5p4j0rLydbNaOr9wOyQrtt3ZQWLYGY9Zees/b8pmcc +Jt+7jstZ2UMV32OO/kIsJ4rMUn2r/uxccPwAc1IDeRSSxOrnFKhW3Cu69iB3bHp7 +JbawY12g7zshE4I14sHjv3QoXASoXjx4xgMCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFI1Fc/Ql2jx+oJPgBVYq +ccgP0pQ8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB4VVVabVp70myuYuZ3vltQIWqSUMhkaTzehMgGcHjMf9iLoZ/I +93KiFUSGnek5cRePyS9wcpp0fcBT3FvkjpUdCjVtdttJgZFhBxgTd8y26ImdDDMR +4+BUuhI5msvjL08f+Vkkpu1GQcGmyFVPFOy/UY8iefu+QyUuiBUnUuEDd49Hw0Fn +/kIPII6Vj82a2mWV/Q8e+rgN8dIRksRjKI03DEoP8lhPlsOkhdwU6Uz9Vu6NOB2Q +Ls1kbcxAc7cFSyRVJEhh12Sz9d0q/CQSTFsVJKOjSNQBQfVnLz1GwO/IieUEAr4C +jkTntH0r1LX5b/GwN4R887LvjAEdTbg1his7 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIkHMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTA2MTc0 +MDIxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtd2VzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDD2yzbbAl77OofTghDMEf624OvU0eS9O+lsdO0QlbfUfWa1Kd6 +0WkgjkLZGfSRxEHMCnrv4UPBSK/Qwn6FTjkDLgemhqBtAnplN4VsoDL+BkRX4Wwq +/dSQJE2b+0hm9w9UMVGFDEq1TMotGGTD2B71eh9HEKzKhGzqiNeGsiX4VV+LJzdH +uM23eGisNqmd4iJV0zcAZ+Gbh2zK6fqTOCvXtm7Idccv8vZZnyk1FiWl3NR4WAgK +AkvWTIoFU3Mt7dIXKKClVmvssG8WHCkd3Xcb4FHy/G756UZcq67gMMTX/9fOFM/v +l5C0+CHl33Yig1vIDZd+fXV1KZD84dEJfEvHAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBR+ap20kO/6A7pPxo3+ +T3CfqZpQWjAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAHCJky2tPjPttlDM/RIqExupBkNrnSYnOK4kr9xJ3sl8UF2DA +PAnYsjXp3rfcjN/k/FVOhxwzi3cXJF/2Tjj39Bm/OEfYTOJDNYtBwB0VVH4ffa/6 +tZl87jaIkrxJcreeeHqYMnIxeN0b/kliyA+a5L2Yb0VPjt9INq34QDc1v74FNZ17 +4z8nr1nzg4xsOWu0Dbjo966lm4nOYIGBRGOKEkHZRZ4mEiMgr3YLkv8gSmeitx57 +Z6dVemNtUic/LVo5Iqw4n3TBS0iF2C1Q1xT/s3h+0SXZlfOWttzSluDvoMv5PvCd +pFjNn+aXLAALoihL1MJSsxydtsLjOBro5eK0Vw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOFAwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAxNzQ2 +MjFaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzU72e6XbaJbi4HjJoRNjKxzUEuChKQIt7k3CWzNnmjc5 +8I1MjCpa2W1iw1BYVysXSNSsLOtUsfvBZxi/1uyMn5ZCaf9aeoA9UsSkFSZBjOCN +DpKPCmfV1zcEOvJz26+1m8WDg+8Oa60QV0ou2AU1tYcw98fOQjcAES0JXXB80P2s +3UfkNcnDz+l4k7j4SllhFPhH6BQ4lD2NiFAP4HwoG6FeJUn45EPjzrydxjq6v5Fc +cQ8rGuHADVXotDbEhaYhNjIrsPL+puhjWfhJjheEw8c4whRZNp6gJ/b6WEes/ZhZ +h32DwsDsZw0BfRDUMgUn8TdecNexHUw8vQWeC181hwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwW9bWgkWkr0U +lrOsq2kvIdrECDgwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAEugF0Gj7HVhX0ehPZoGRYRt3PBuI2YjfrrJRTZ9X5wc +9T8oHmw07mHmNy1qqWvooNJg09bDGfB0k5goC2emDiIiGfc/kvMLI7u+eQOoMKj6 +mkfCncyRN3ty08Po45vTLBFZGUvtQmjM6yKewc4sXiASSBmQUpsMbiHRCL72M5qV +obcJOjGcIdDTmV1BHdWT+XcjynsGjUqOvQWWhhLPrn4jWe6Xuxll75qlrpn3IrIx +CRBv/5r7qbcQJPOgwQsyK4kv9Ly8g7YT1/vYBlR3cRsYQjccw5ceWUj2DrMVWhJ4 +prf+E3Aa4vYmLLOUUvKnDQ1k3RGNu56V0tonsQbfsaM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICEzUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTAyMDUy +MjVaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAOxHqdcPSA2uBjsCP4DLSlqSoPuQ/X1kkJLusVRKiQE2zayB +viuCBt4VB9Qsh2rW3iYGM+usDjltGnI1iUWA5KHcvHszSMkWAOYWLiMNKTlg6LCp +XnE89tvj5dIH6U8WlDvXLdjB/h30gW9JEX7S8supsBSci2GxEzb5mRdKaDuuF/0O +qvz4YE04pua3iZ9QwmMFuTAOYzD1M72aOpj+7Ac+YLMM61qOtU+AU6MndnQkKoQi +qmUN2A9IFaqHFzRlSdXwKCKUA4otzmz+/N3vFwjb5F4DSsbsrMfjeHMo6o/nb6Nh +YDb0VJxxPee6TxSuN7CQJ2FxMlFUezcoXqwqXD0CAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFDGGpon9WfIpsggE +CxHq8hZ7E2ESMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQAvpeQYEGZvoTVLgV9rd2+StPYykMsmFjWQcyn3dBTZRXC2 +lKq7QhQczMAOhEaaN29ZprjQzsA2X/UauKzLR2Uyqc2qOeO9/YOl0H3qauo8C/W9 +r8xqPbOCDLEXlOQ19fidXyyEPHEq5WFp8j+fTh+s8WOx2M7IuC0ANEetIZURYhSp +xl9XOPRCJxOhj7JdelhpweX0BJDNHeUFi0ClnFOws8oKQ7sQEv66d5ddxqqZ3NVv +RbCvCtEutQMOUMIuaygDlMn1anSM8N7Wndx8G6+Uy67AnhjGx7jw/0YPPxopEj6x +JXP8j0sJbcT9K/9/fPVLNT25RvQ/93T2+IQL4Ca2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICYpgwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExNzMx +NDhaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMk3YdSZ64iAYp6MyyKtYJtNzv7zFSnnNf6vv0FB4VnfITTMmOyZ +LXqKAT2ahZ00hXi34ewqJElgU6eUZT/QlzdIu359TEZyLVPwURflL6SWgdG01Q5X +O++7fSGcBRyIeuQWs9FJNIIqK8daF6qw0Rl5TXfu7P9dBc3zkgDXZm2DHmxGDD69 +7liQUiXzoE1q2Z9cA8+jirDioJxN9av8hQt12pskLQumhlArsMIhjhHRgF03HOh5 +tvi+RCfihVOxELyIRTRpTNiIwAqfZxxTWFTgfn+gijTmd0/1DseAe82aYic8JbuS +EMbrDduAWsqrnJ4GPzxHKLXX0JasCUcWyMECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPLtsq1NrwJXO13C9eHt +sLY11AGwMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAnWBKj5xV1A1mYd0kIgDdkjCwQkiKF5bjIbGkT3YEFFbXoJlSP +0lZZ/hDaOHI8wbLT44SzOvPEEmWF9EE7SJzkvSdQrUAWR9FwDLaU427ALI3ngNHy +lGJ2hse1fvSRNbmg8Sc9GBv8oqNIBPVuw+AJzHTacZ1OkyLZrz1c1QvwvwN2a+Jd +vH0V0YIhv66llKcYDMUQJAQi4+8nbRxXWv6Gq3pvrFoorzsnkr42V3JpbhnYiK+9 +nRKd4uWl62KRZjGkfMbmsqZpj2fdSWMY1UGyN1k+kDmCSWYdrTRDP0xjtIocwg+A +J116n4hV/5mbA0BaPiS2krtv17YAeHABZcvz +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECjCCAvKgAwIBAgICV2YwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTExOTM2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGXMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEoMCYGA1UEAwwfQW1h +em9uIFJEUyBldS1jZW50cmFsLTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBAMEx54X2pHVv86APA0RWqxxRNmdkhAyp2R1cFWumKQRofoFv +n+SPXdkpIINpMuEIGJANozdiEz7SPsrAf8WHyD93j/ZxrdQftRcIGH41xasetKGl +I67uans8d+pgJgBKGb/Z+B5m+UsIuEVekpvgpwKtmmaLFC/NCGuSsJoFsRqoa6Gh +m34W6yJoY87UatddCqLY4IIXaBFsgK9Q/wYzYLbnWM6ZZvhJ52VMtdhcdzeTHNW0 +5LGuXJOF7Ahb4JkEhoo6TS2c0NxB4l4MBfBPgti+O7WjR3FfZHpt18A6Zkq6A2u6 +D/oTSL6c9/3sAaFTFgMyL3wHb2YlW0BPiljZIqECAwEAAaNmMGQwDgYDVR0PAQH/ +BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFOcAToAc6skWffJa +TnreaswAfrbcMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqG +SIb3DQEBCwUAA4IBAQA1d0Whc1QtspK496mFWfFEQNegLh0a9GWYlJm+Htcj5Nxt +DAIGXb+8xrtOZFHmYP7VLCT5Zd2C+XytqseK/+s07iAr0/EPF+O2qcyQWMN5KhgE +cXw2SwuP9FPV3i+YAm11PBVeenrmzuk9NrdHQ7TxU4v7VGhcsd2C++0EisrmquWH +mgIfmVDGxphwoES52cY6t3fbnXmTkvENvR+h3rj+fUiSz0aSo+XZUGHPgvuEKM/W +CBD9Smc9CBoBgvy7BgHRgRUmwtABZHFUIEjHI5rIr7ZvYn+6A0O6sogRfvVYtWFc +qpyrW1YX8mD0VlJ8fGKM3G+aCOsiiPKDV/Uafrm+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgICGAcwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIxODE5 +NDRaFw0yNDA4MjIxNzA4NTBaMIGVMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEmMCQGA1UEAwwdQW1h +em9uIFJEUyBldS1ub3J0aC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQCiIYnhe4UNBbdBb/nQxl5giM0XoVHWNrYV5nB0YukA98+TPn9v +Aoj1RGYmtryjhrf01Kuv8SWO+Eom95L3zquoTFcE2gmxCfk7bp6qJJ3eHOJB+QUO +XsNRh76fwDzEF1yTeZWH49oeL2xO13EAx4PbZuZpZBttBM5zAxgZkqu4uWQczFEs +JXfla7z2fvWmGcTagX10O5C18XaFroV0ubvSyIi75ue9ykg/nlFAeB7O0Wxae88e +uhiBEFAuLYdqWnsg3459NfV8Yi1GnaitTym6VI3tHKIFiUvkSiy0DAlAGV2iiyJE +q+DsVEO4/hSINJEtII4TMtysOsYPpINqeEzRAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBRR0UpnbQyjnHChgmOc +hnlc0PogzTAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAKJD4xVzSf4zSGTBJrmamo86jl1NHQxXUApAZuBZEc8tqC6TI +T5CeoSr9CMuVC8grYyBjXblC4OsM5NMvmsrXl/u5C9dEwtBFjo8mm53rOOIm1fxl +I1oYB/9mtO9ANWjkykuLzWeBlqDT/i7ckaKwalhLODsRDO73vRhYNjsIUGloNsKe +pxw3dzHwAZx4upSdEVG4RGCZ1D0LJ4Gw40OfD69hfkDfRVVxKGrbEzqxXRvovmDc +tKLdYZO/6REoca36v4BlgIs1CbUXJGLSXUwtg7YXGLSVBJ/U0+22iGJmBSNcoyUN +cjPFD9JQEhDDIYYKSGzIYpvslvGc4T5ISXFiuQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICZIEwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTIyMTMy +MzJaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBALGiwqjiF7xIjT0Sx7zB3764K2T2a1DHnAxEOr+/EIftWKxWzT3u +PFwS2eEZcnKqSdRQ+vRzonLBeNLO4z8aLjQnNbkizZMBuXGm4BqRm1Kgq3nlLDQn +7YqdijOq54SpShvR/8zsO4sgMDMmHIYAJJOJqBdaus2smRt0NobIKc0liy7759KB +6kmQ47Gg+kfIwxrQA5zlvPLeQImxSoPi9LdbRoKvu7Iot7SOa+jGhVBh3VdqndJX +7tm/saj4NE375csmMETFLAOXjat7zViMRwVorX4V6AzEg1vkzxXpA9N7qywWIT5Y +fYaq5M8i6vvLg0CzrH9fHORtnkdjdu1y+0MCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFFOhOx1yt3Z7mvGB9jBv +2ymdZwiOMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBehqY36UGDvPVU9+vtaYGr38dBbp+LzkjZzHwKT1XJSSUc2wqM +hnCIQKilonrTIvP1vmkQi8qHPvDRtBZKqvz/AErW/ZwQdZzqYNFd+BmOXaeZWV0Q +oHtDzXmcwtP8aUQpxN0e1xkWb1E80qoy+0uuRqb/50b/R4Q5qqSfJhkn6z8nwB10 +7RjLtJPrK8igxdpr3tGUzfAOyiPrIDncY7UJaL84GFp7WWAkH0WG3H8Y8DRcRXOU +mqDxDLUP3rNuow3jnGxiUY+gGX5OqaZg4f4P6QzOSmeQYs6nLpH0PiN00+oS1BbD +bpWdZEttILPI+vAYkU4QuBKKDjJL6HbSd+cn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIDAIVCMA0GCSqGSIb3DQEBCwUAMIGPMQswCQYDVQQGEwJV +UzEQMA4GA1UEBwwHU2VhdHRsZTETMBEGA1UECAwKV2FzaGluZ3RvbjEiMCAGA1UE +CgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJE +UzEgMB4GA1UEAwwXQW1hem9uIFJEUyBSb290IDIwMTkgQ0EwHhcNMTkwOTEzMTcw +NjQxWhcNMjQwODIyMTcwODUwWjCBlDELMAkGA1UEBhMCVVMxEzARBgNVBAgMCldh +c2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxIjAgBgNVBAoMGUFtYXpvbiBXZWIg +U2VydmljZXMsIEluYy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxJTAjBgNVBAMMHEFt +YXpvbiBSRFMgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDE+T2xYjUbxOp+pv+gRA3FO24+1zCWgXTDF1DHrh1lsPg5k7ht +2KPYzNc+Vg4E+jgPiW0BQnA6jStX5EqVh8BU60zELlxMNvpg4KumniMCZ3krtMUC +au1NF9rM7HBh+O+DYMBLK5eSIVt6lZosOb7bCi3V6wMLA8YqWSWqabkxwN4w0vXI +8lu5uXXFRemHnlNf+yA/4YtN4uaAyd0ami9+klwdkZfkrDOaiy59haOeBGL8EB/c +dbJJlguHH5CpCscs3RKtOOjEonXnKXldxarFdkMzi+aIIjQ8GyUOSAXHtQHb3gZ4 +nS6Ey0CMlwkB8vUObZU9fnjKJcL5QCQqOfwvAgMBAAGjZjBkMA4GA1UdDwEB/wQE +AwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBQUPuRHohPxx4VjykmH +6usGrLL1ETAfBgNVHSMEGDAWgBRzX2DYvMsDmPQrFzQuNlqmYP+8HzANBgkqhkiG +9w0BAQsFAAOCAQEAUdR9Vb3y33Yj6X6KGtuthZ08SwjImVQPtknzpajNE5jOJAh8 +quvQnU9nlnMO85fVDU1Dz3lLHGJ/YG1pt1Cqq2QQ200JcWCvBRgdvH6MjHoDQpqZ +HvQ3vLgOGqCLNQKFuet9BdpsHzsctKvCVaeBqbGpeCtt3Hh/26tgx0rorPLw90A2 +V8QSkZJjlcKkLa58N5CMM8Xz8KLWg3MZeT4DmlUXVCukqK2RGuP2L+aME8dOxqNv +OnOz1zrL5mR2iJoDpk8+VE/eBDmJX40IJk6jBjWoxAO/RXq+vBozuF5YHN1ujE92 +tO8HItgTp37XT8bJBAiAnt5mxw+NLSqtxk2QdQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICY4kwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTMyMDEx +NDJaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAr5u9OuLL/OF/fBNUX2kINJLzFl4DnmrhnLuSeSnBPgbb +qddjf5EFFJBfv7IYiIWEFPDbDG5hoBwgMup5bZDbas+ZTJTotnnxVJTQ6wlhTmns +eHECcg2pqGIKGrxZfbQhlj08/4nNAPvyYCTS0bEcmQ1emuDPyvJBYDDLDU6AbCB5 +6Z7YKFQPTiCBblvvNzchjLWF9IpkqiTsPHiEt21sAdABxj9ityStV3ja/W9BfgxH +wzABSTAQT6FbDwmQMo7dcFOPRX+hewQSic2Rn1XYjmNYzgEHisdUsH7eeXREAcTw +61TRvaLH8AiOWBnTEJXPAe6wYfrcSd1pD0MXpoB62wIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUytwMiomQOgX5 +Ichd+2lDWRUhkikwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBACf6lRDpfCD7BFRqiWM45hqIzffIaysmVfr+Jr+fBTjP +uYe/ba1omSrNGG23bOcT9LJ8hkQJ9d+FxUwYyICQNWOy6ejicm4z0C3VhphbTPqj +yjpt9nG56IAcV8BcRJh4o/2IfLNzC/dVuYJV8wj7XzwlvjysenwdrJCoLadkTr1h +eIdG6Le07sB9IxrGJL9e04afk37h7c8ESGSE4E+oS4JQEi3ATq8ne1B9DQ9SasXi +IRmhNAaISDzOPdyLXi9N9V9Lwe/DHcja7hgLGYx3UqfjhLhOKwp8HtoZORixAmOI +HfILgNmwyugAbuZoCazSKKBhQ0wgO0WZ66ZKTMG8Oho= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICUYkwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxODIx +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy13ZXN0LTIgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBANCEZBZyu6yJQFZBJmSUZfSZd3Ui2gitczMKC4FLr0QzkbxY+cLa +uVONIOrPt4Rwi+3h/UdnUg917xao3S53XDf1TDMFEYp4U8EFPXqCn/GXBIWlU86P +PvBN+gzw3nS+aco7WXb+woTouvFVkk8FGU7J532llW8o/9ydQyDIMtdIkKTuMfho +OiNHSaNc+QXQ32TgvM9A/6q7ksUoNXGCP8hDOkSZ/YOLiI5TcdLh/aWj00ziL5bj +pvytiMZkilnc9dLY9QhRNr0vGqL0xjmWdoEXz9/OwjmCihHqJq+20MJPsvFm7D6a +2NKybR9U+ddrjb8/iyLOjURUZnj5O+2+OPcCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFEBxMBdv81xuzqcK5TVu +pHj+Aor8MB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQBZkfiVqGoJjBI37aTlLOSjLcjI75L5wBrwO39q+B4cwcmpj58P +3sivv+jhYfAGEbQnGRzjuFoyPzWnZ1DesRExX+wrmHsLLQbF2kVjLZhEJMHF9eB7 +GZlTPdTzHErcnuXkwA/OqyXMpj9aghcQFuhCNguEfnROY9sAoK2PTfnTz9NJHL+Q +UpDLEJEUfc0GZMVWYhahc0x38ZnSY2SKacIPECQrTI0KpqZv/P+ijCEcMD9xmYEb +jL4en+XKS1uJpw5fIU5Sj0MxhdGstH6S84iAE5J3GM3XHklGSFwwqPYvuTXvANH6 +uboynxRgSae59jIlAK6Jrr6GWMwQRbgcaAlW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICEkYwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTYxOTUz +NDdaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMiAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAufodI2Flker8q7PXZG0P0vmFSlhQDw907A6eJuF/WeMo +GHnll3b4S6nC3oRS3nGeRMHbyU2KKXDwXNb3Mheu+ox+n5eb/BJ17eoj9HbQR1cd +gEkIciiAltf8gpMMQH4anP7TD+HNFlZnP7ii3geEJB2GGXSxgSWvUzH4etL67Zmn +TpGDWQMB0T8lK2ziLCMF4XAC/8xDELN/buHCNuhDpxpPebhct0T+f6Arzsiswt2j +7OeNeLLZwIZvVwAKF7zUFjC6m7/VmTQC8nidVY559D6l0UhhU0Co/txgq3HVsMOH +PbxmQUwJEKAzQXoIi+4uZzHFZrvov/nDTNJUhC6DqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUwaZpaCme+EiV +M5gcjeHZSTgOn4owHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAR6a2meCZuXO2TF9bGqKGtZmaah4pH2ETcEVUjkvXVz +sl+ZKbYjrun+VkcMGGKLUjS812e7eDF726ptoku9/PZZIxlJB0isC/0OyixI8N4M +NsEyvp52XN9QundTjkl362bomPnHAApeU0mRbMDRR2JdT70u6yAzGLGsUwMkoNnw +1VR4XKhXHYGWo7KMvFrZ1KcjWhubxLHxZWXRulPVtGmyWg/MvE6KF+2XMLhojhUL ++9jB3Fpn53s6KMx5tVq1x8PukHmowcZuAF8k+W4gk8Y68wIwynrdZrKRyRv6CVtR +FZ8DeJgoNZT3y/GT254VqMxxfuy2Ccb/RInd16tEvVk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICOYIwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTcyMDA1 +MjlaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEA4dMak8W+XW8y/2F6nRiytFiA4XLwePadqWebGtlIgyCS +kbug8Jv5w7nlMkuxOxoUeD4WhI6A9EkAn3r0REM/2f0aYnd2KPxeqS2MrtdxxHw1 +xoOxk2x0piNSlOz6yog1idsKR5Wurf94fvM9FdTrMYPPrDabbGqiBMsZZmoHLvA3 +Z+57HEV2tU0Ei3vWeGIqnNjIekS+E06KhASxrkNU5vi611UsnYZlSi0VtJsH4UGV +LhnHl53aZL0YFO5mn/fzuNG/51qgk/6EFMMhaWInXX49Dia9FnnuWXwVwi6uX1Wn +7kjoHi5VtmC8ZlGEHroxX2DxEr6bhJTEpcLMnoQMqwIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUsUI5Cb3SWB8+ +gv1YLN/ABPMdxSAwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAJAF3E9PM1uzVL8YNdzb6fwJrxxqI2shvaMVmC1mXS+w +G0zh4v2hBZOf91l1EO0rwFD7+fxoI6hzQfMxIczh875T6vUXePKVOCOKI5wCrDad +zQbVqbFbdhsBjF4aUilOdtw2qjjs9JwPuB0VXN4/jY7m21oKEOcnpe36+7OiSPjN +xngYewCXKrSRqoj3mw+0w/+exYj3Wsush7uFssX18av78G+ehKPIVDXptOCP/N7W +8iKVNeQ2QGTnu2fzWsGUSvMGyM7yqT+h1ILaT//yQS8er511aHMLc142bD4D9VSy +DgactwPDTShK/PXqhvNey9v/sKXm4XatZvwcc8KYlW4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDDCCAvSgAwIBAgICcEUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNjU2 +MjBaFw0yNDA4MjIxNzA4NTBaMIGZMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzEqMCgGA1UEAwwhQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMSAyMDE5IENBMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAndtkldmHtk4TVQAyqhAvtEHSMb6pLhyKrIFved1WO3S7 ++I+bWwv9b2W/ljJxLq9kdT43bhvzonNtI4a1LAohS6bqyirmk8sFfsWT3akb+4Sx +1sjc8Ovc9eqIWJCrUiSvv7+cS7ZTA9AgM1PxvHcsqrcUXiK3Jd/Dax9jdZE1e15s +BEhb2OEPE+tClFZ+soj8h8Pl2Clo5OAppEzYI4LmFKtp1X/BOf62k4jviXuCSst3 +UnRJzE/CXtjmN6oZySVWSe0rQYuyqRl6//9nK40cfGKyxVnimB8XrrcxUN743Vud +QQVU0Esm8OVTX013mXWQXJHP2c0aKkog8LOga0vobQIDAQABo2YwZDAOBgNVHQ8B +Af8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQULmoOS1mFSjj+ +snUPx4DgS3SkLFYwHwYDVR0jBBgwFoAUc19g2LzLA5j0Kxc0LjZapmD/vB8wDQYJ +KoZIhvcNAQELBQADggEBAAkVL2P1M2/G9GM3DANVAqYOwmX0Xk58YBHQu6iiQg4j +b4Ky/qsZIsgT7YBsZA4AOcPKQFgGTWhe9pvhmXqoN3RYltN8Vn7TbUm/ZVDoMsrM +gwv0+TKxW1/u7s8cXYfHPiTzVSJuOogHx99kBW6b2f99GbP7O1Sv3sLq4j6lVvBX +Fiacf5LAWC925nvlTzLlBgIc3O9xDtFeAGtZcEtxZJ4fnGXiqEnN4539+nqzIyYq +nvlgCzyvcfRAxwltrJHuuRu6Maw5AGcd2Y0saMhqOVq9KYKFKuD/927BTrbd2JVf +2sGWyuPZPCk3gq+5pCjbD0c6DkhcMGI6WwxvM5V/zSM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJDQwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTgxNzAz +MTVaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyBldS13ZXN0LTMgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAL9bL7KE0n02DLVtlZ2PL+g/BuHpMYFq2JnE2RgompGurDIZdjmh +1pxfL3nT+QIVMubuAOy8InRfkRxfpxyjKYdfLJTPJG+jDVL+wDcPpACFVqoV7Prg +pVYEV0lc5aoYw4bSeYFhdzgim6F8iyjoPnObjll9mo4XsHzSoqJLCd0QC+VG9Fw2 +q+GDRZrLRmVM2oNGDRbGpGIFg77aRxRapFZa8SnUgs2AqzuzKiprVH5i0S0M6dWr +i+kk5epmTtkiDHceX+dP/0R1NcnkCPoQ9TglyXyPdUdTPPRfKCq12dftqll+u4mV +ARdN6WFjovxax8EAP2OAUTi1afY+1JFMj+sCAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFLfhrbrO5exkCVgxW0x3 +Y2mAi8lNMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQAigQ5VBNGyw+OZFXwxeJEAUYaXVoP/qrhTOJ6mCE2DXUVEoJeV +SxScy/TlFA9tJXqmit8JH8VQ/xDL4ubBfeMFAIAo4WzNWDVoeVMqphVEcDWBHsI1 +AETWzfsapRS9yQekOMmxg63d/nV8xewIl8aNVTHdHYXMqhhik47VrmaVEok1UQb3 +O971RadLXIEbVd9tjY5bMEHm89JsZDnDEw1hQXBb67Elu64OOxoKaHBgUH8AZn/2 +zFsL1ynNUjOhCSAA15pgd1vjwc0YsBbAEBPcHBWYBEyME6NLNarjOzBl4FMtATSF +wWCKRGkvqN8oxYhwR2jf2rR5Mu4DWkK5Q8Ep +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBzCCAu+gAwIBAgICJVUwDQYJKoZIhvcNAQELBQAwgY8xCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSAwHgYDVQQDDBdBbWF6b24gUkRTIFJvb3QgMjAxOSBDQTAeFw0xOTA5MTkxODE2 +NTNaFw0yNDA4MjIxNzA4NTBaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKV2Fz +aGluZ3RvbjEQMA4GA1UEBwwHU2VhdHRsZTEiMCAGA1UECgwZQW1hem9uIFdlYiBT +ZXJ2aWNlcywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzElMCMGA1UEAwwcQW1h +em9uIFJEUyB1cy1lYXN0LTEgMjAxOSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAM3i/k2u6cqbMdcISGRvh+m+L0yaSIoOXjtpNEoIftAipTUYoMhL +InXGlQBVA4shkekxp1N7HXe1Y/iMaPEyb3n+16pf3vdjKl7kaSkIhjdUz3oVUEYt +i8Z/XeJJ9H2aEGuiZh3kHixQcZczn8cg3dA9aeeyLSEnTkl/npzLf//669Ammyhs +XcAo58yvT0D4E0D/EEHf2N7HRX7j/TlyWvw/39SW0usiCrHPKDLxByLojxLdHzso +QIp/S04m+eWn6rmD+uUiRteN1hI5ncQiA3wo4G37mHnUEKo6TtTUh+sd/ku6a8HK +glMBcgqudDI90s1OpuIAWmuWpY//8xEG2YECAwEAAaNmMGQwDgYDVR0PAQH/BAQD +AgEGMBIGA1UdEwEB/wQIMAYBAf8CAQAwHQYDVR0OBBYEFPqhoWZcrVY9mU7tuemR +RBnQIj1jMB8GA1UdIwQYMBaAFHNfYNi8ywOY9CsXNC42WqZg/7wfMA0GCSqGSIb3 +DQEBCwUAA4IBAQB6zOLZ+YINEs72heHIWlPZ8c6WY8MDU+Be5w1M+BK2kpcVhCUK +PJO4nMXpgamEX8DIiaO7emsunwJzMSvavSPRnxXXTKIc0i/g1EbiDjnYX9d85DkC +E1LaAUCmCZBVi9fIe0H2r9whIh4uLWZA41oMnJx/MOmo3XyMfQoWcqaSFlMqfZM4 +0rNoB/tdHLNuV4eIdaw2mlHxdWDtF4oH+HFm+2cVBUVC1jXKrFv/euRVtsTT+A6i +h2XBHKxQ1Y4HgAn0jACP2QSPEmuoQEIa57bEKEcZsBR8SDY6ZdTd2HLRIApcCOSF +MRM8CKLeF658I0XgF8D5EsYoKPsA+74Z+jDH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEETCCAvmgAwIBAgICEAAwDQYJKoZIhvcNAQELBQAwgZQxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSUwIwYDVQQDDBxBbWF6b24gUkRTIEJldGEgUm9vdCAyMDE5IENBMB4XDTE5MDgy +MDE3MTAwN1oXDTI0MDgxOTE3MzgyNlowgZkxCzAJBgNVBAYTAlVTMRMwEQYDVQQI +DApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6b24g +V2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMSowKAYDVQQD +DCFBbWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIDIwMTkgQ0EwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDTNCOlotQcLP8TP82U2+nk0bExVuuMVOgFeVMx +vbUHZQeIj9ikjk+jm6eTDnnkhoZcmJiJgRy+5Jt69QcRbb3y3SAU7VoHgtraVbxF +QDh7JEHI9tqEEVOA5OvRrDRcyeEYBoTDgh76ROco2lR+/9uCvGtHVrMCtG7BP7ZB +sSVNAr1IIRZZqKLv2skKT/7mzZR2ivcw9UeBBTUf8xsfiYVBvMGoEsXEycjYdf6w +WV+7XS7teNOc9UgsFNN+9AhIBc1jvee5E//72/4F8pAttAg/+mmPUyIKtekNJ4gj +OAR2VAzGx1ybzWPwIgOudZFHXFduxvq4f1hIRPH0KbQ/gkRrAgMBAAGjZjBkMA4G +A1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBTkvpCD +6C43rar9TtJoXr7q8dkrrjAfBgNVHSMEGDAWgBStoQwVpbGx87fxB3dEGDqKKnBT +4TANBgkqhkiG9w0BAQsFAAOCAQEAJd9fOSkwB3uVdsS+puj6gCER8jqmhd3g/J5V +Zjk9cKS8H0e8pq/tMxeJ8kpurPAzUk5RkCspGt2l0BSwmf3ahr8aJRviMX6AuW3/ +g8aKplTvq/WMNGKLXONa3Sq8591J+ce8gtOX/1rDKmFI4wQ/gUzOSYiT991m7QKS +Fr6HMgFuz7RNJbb3Fy5cnurh8eYWA7mMv7laiLwTNsaro5qsqErD5uXuot6o9beT +a+GiKinEur35tNxAr47ax4IRubuIzyfCrezjfKc5raVV2NURJDyKP0m0CCaffAxE +qn2dNfYc3v1D8ypg3XjHlOzRo32RB04o8ALHMD9LSwsYDLpMag== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEFzCCAv+gAwIBAgICFSUwDQYJKoZIhvcNAQELBQAwgZcxCzAJBgNVBAYTAlVT +MRAwDgYDVQQHDAdTZWF0dGxlMRMwEQYDVQQIDApXYXNoaW5ndG9uMSIwIAYDVQQK +DBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRT +MSgwJgYDVQQDDB9BbWF6b24gUkRTIFByZXZpZXcgUm9vdCAyMDE5IENBMB4XDTE5 +MDgyMTIyMzk0N1oXDTI0MDgyMTIyMjk0OVowgZwxCzAJBgNVBAYTAlVTMRMwEQYD +VQQIDApXYXNoaW5ndG9uMRAwDgYDVQQHDAdTZWF0dGxlMSIwIAYDVQQKDBlBbWF6 +b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMS0wKwYD +VQQDDCRBbWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIDIwMTkgQ0EwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD0dB/U7qRnSf05wOi7m10Pa2uPMTJv +r6U/3Y17a5prq5Zr4++CnSUYarG51YuIf355dKs+7Lpzs782PIwCmLpzAHKWzix6 +pOaTQ+WZ0+vUMTxyqgqWbsBgSCyP7pVBiyqnmLC/L4az9XnscrbAX4pNaoJxsuQe +mzBo6yofjQaAzCX69DuqxFkVTRQnVy7LCFkVaZtjNAftnAHJjVgQw7lIhdGZp9q9 +IafRt2gteihYfpn+EAQ/t/E4MnhrYs4CPLfS7BaYXBycEKC5Muj1l4GijNNQ0Efo +xG8LSZz7SNgUvfVwiNTaqfLP3AtEAWiqxyMyh3VO+1HpCjT7uNBFtmF3AgMBAAGj +ZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQW +BBQtinkdrj+0B2+qdXngV2tgHnPIujAfBgNVHSMEGDAWgBRp0xqULkNh/w2ZVzEI +o2RIY7O03TANBgkqhkiG9w0BAQsFAAOCAQEAtJdqbCxDeMc8VN1/RzCabw9BIL/z +73Auh8eFTww/sup26yn8NWUkfbckeDYr1BrXa+rPyLfHpg06kwR8rBKyrs5mHwJx +bvOzXD/5WTdgreB+2Fb7mXNvWhenYuji1MF+q1R2DXV3I05zWHteKX6Dajmx+Uuq +Yq78oaCBSV48hMxWlp8fm40ANCL1+gzQ122xweMFN09FmNYFhwuW+Ao+Vv90ZfQG +PYwTvN4n/gegw2TYcifGZC2PNX74q3DH03DXe5fvNgRW5plgz/7f+9mS+YHd5qa9 +tYTPUvoRbi169ou6jicsMKUKPORHWhiTpSCWR1FMMIbsAcsyrvtIsuaGCQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQdOCSuA9psBpQd8EI368/0DANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTgwNjI2WhgPMjA2MTA1MTkxOTA2MjZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgc2EtZWFzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN6ftL6w8v3dB2yW +LjCxSP1D7ZsOTeLZOSCz1Zv0Gkd0XLhil5MdHOHBvwH/DrXqFU2oGzCRuAy+aZis +DardJU6ChyIQIciXCO37f0K23edhtpXuruTLLwUwzeEPdcnLPCX+sWEn9Y5FPnVm +pCd6J8edH2IfSGoa9LdErkpuESXdidLym/w0tWG/O2By4TabkNSmpdrCL00cqI+c +prA8Bx1jX8/9sY0gpAovtuFaRN+Ivg3PAnWuhqiSYyQ5nC2qDparOWuDiOhpY56E +EgmTvjwqMMjNtExfYx6Rv2Ndu50TriiNKEZBzEtkekwXInTupmYTvc7U83P/959V +UiQ+WSMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU4uYHdH0+ +bUeh81Eq2l5/RJbW+vswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBhxcExJ+w74bvDknrPZDRgTeMLYgbVJjx2ExH7/Ac5FZZWcpUpFwWMIJJxtewI +AnhryzM3tQYYd4CG9O+Iu0+h/VVfW7e4O3joWVkxNMb820kQSEwvZfA78aItGwOY +WSaFNVRyloVicZRNJSyb1UL9EiJ9ldhxm4LTT0ax+4ontI7zTx6n6h8Sr6r/UOvX +d9T5aUUENWeo6M9jGupHNn3BobtL7BZm2oS8wX8IVYj4tl0q5T89zDi2x0MxbsIV +5ZjwqBQ5JWKv7ASGPb+z286RjPA9R2knF4lJVZrYuNV90rHvI/ECyt/JrDqeljGL +BLl1W/UsvZo6ldLIpoMbbrb5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQUfVbqapkLYpUqcLajpTJWzANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA2MjMyMDA5WhgPMjA2MjA1MDcwMDIwMDlaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJIeovu3 +ewI9FVitXMQzvkh34aQ6WyI4NO3YepfJaePiv3cnyFGYHN2S1cR3UQcLWgypP5va +j6bfroqwGbCbZZcb+6cyOB4ceKO9Ws1UkcaGHnNDcy5gXR7aCW2OGTUfinUuhd2d +5bOGgV7JsPbpw0bwJ156+MwfOK40OLCWVbzy8B1kITs4RUPNa/ZJnvIbiMu9rdj4 +8y7GSFJLnKCjlOFUkNI5LcaYvI1+ybuNgphT3nuu5ZirvTswGakGUT/Q0J3dxP0J +pDfg5Sj/2G4gXiaM0LppVOoU5yEwVewhQ250l0eQAqSrwPqAkdTg9ng360zqCFPE +JPPcgI1tdGUgneECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +/2AJVxWdZxc8eJgdpbwpW7b0f7IwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQBYm63jTu2qYKJ94gKnqc+oUgqmb1mTXmgmp/lXDbxonjszJDOXFbri +3CCO7xB2sg9bd5YWY8sGKHaWmENj3FZpCmoefbUx++8D7Mny95Cz8R32rNcwsPTl +ebpd9A/Oaw5ug6M0x/cNr0qzF8Wk9Dx+nFEimp8RYQdKvLDfNFZHjPa1itnTiD8M +TorAqj+VwnUGHOYBsT/0NY12tnwXdD+ATWfpEHdOXV+kTMqFFwDyhfgRVNpTc+os +ygr8SwhnSCpJPB/EYl2S7r+tgAbJOkuwUvGT4pTqrzDQEhwE7swgepnHC87zhf6l +qN6mVpSnQKQLm6Ob5TeCEFgcyElsF5bH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAOxu0I1QuMAhIeszB3fJIlkwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjIwNjU5WhgPMjEyMTA1MjQyMzA2NTlaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEz4bylRcGqqDWdP7gQIIoTHdBK6FNtKH1 +4SkEIXRXkYDmRvL9Bci1MuGrwuvrka5TDj4b7e+csY0llEzHpKfq6nJPFljoYYP9 +uqHFkv77nOpJJ633KOr8IxmeHW5RXgrZo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQQikVz8wmjd9eDFRXzBIU8OseiGzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwf06Mcrpw1O0EBLBBrp84m37NYtOkE/0Z0O+C7D41wnXi +EQdn6PXUVgdD23Gj82SrAjEAklhKs+liO1PtN15yeZR1Io98nFve+lLptaLakZcH ++hfFuUtCqMbaI8CdvJlKnPqT +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRALyWMTyCebLZOGcZZQmkmfcwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyODAzWhgPMjEyMTA1MjQyMTI4MDNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +wGFiyDyCrGqgdn4fXG12cxKAAfVvhMea1mw5h9CVRoavkPqhzQpAitSOuMB9DeiP +wQyqcsiGl/cTEau4L+AUBG8b9v26RlY48exUYBXj8CieYntOT9iNw5WtdYJa3kF/ +JxgI+HDMzE9cmHDs5DOO3S0uwZVyra/xE1ymfSlpOeUIOTpHRJv97CBUEpaZMUW5 +Sr6GruuOwFVpO5FX3A/jQlcS+UN4GjSRgDUJuqg6RRQldEZGCVCCmodbByvI2fGm +reGpsPJD54KkmAX08nOR8e5hkGoHxq0m2DLD4SrOFmt65vG47qnuwplWJjtk9B3Z +9wDoopwZLBOtlkPIkUllWm1P8EuHC1IKOA+wSP6XdT7cy8S77wgyHzR0ynxv7q/l +vlZtH30wnNqFI0y9FeogD0TGMCHcnGqfBSicJXPy9T4fU6f0r1HwqKwPp2GArwe7 +dnqLTj2D7M9MyVtFjEs6gfGWXmu1y5uDrf+CszurE8Cycoma+OfjjuVQgWOCy7Nd +jJswPxAroTzVfpgoxXza4ShUY10woZu0/J+HmNmqK7lh4NS75q1tz75in8uTZDkV +be7GK+SEusTrRgcf3tlgPjSTWG3veNzFDF2Vn1GLJXmuZfhdlVQDBNXW4MNREExS +dG57kJjICpT+r8X+si+5j51gRzkSnMYs7VHulpxfcwECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQU4JWOpDBmUBuWKvGPZelw87ezhL8wDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBRNLMql7itvXSEFQRAnyOjivHz +l5IlWVQjAbOUr6ogZcwvK6YpxNAFW5zQr8F+fdkiypLz1kk5irx9TIpff0BWC9hQ +/odMPO8Gxn8+COlSvc+dLsF2Dax3Hvz0zLeKMo+cYisJOzpdR/eKd0/AmFdkvQoM +AOK9n0yYvVJU2IrSgeJBiiCarpKSeAktEVQ4rvyacQGr+QAPkkjRwm+5LHZKK43W +nNnggRli9N/27qYtc5bgr3AaQEhEXMI4RxPRXCLsod0ehMGWyRRK728a+6PMMJAJ +WHOU0x7LCEMPP/bvpLj3BdvSGqNor4ZtyXEbwREry1uzsgODeRRns5acPwTM6ff+ +CmxO2NZ0OktIUSYRmf6H/ZFlZrIhV8uWaIwEJDz71qvj7buhQ+RFDZ9CNL64C0X6 +mf0zJGEpddjANHaaVky+F4gYMtEy2K2Lcm4JGTdyIzUoIe+atzCnRp0QeIcuWtF+ +s8AjDYCVFNypcMmqbRmNpITSnOoCHSRuVkY3gutVoYyMLbp8Jm9SJnCIlEWTA6Rm +wADOMGZJVn5/XRTRuetVOB3KlQDjs9OO01XN5NzGSZO2KT9ngAUfh9Eqhf1iRWSP +nZlRbQ2NRCuY/oJ5N59mLGxnNJSE7giEKEBRhTQ/XEPIUYAUPD5fca0arKRJwbol +l9Se1Hsq0ZU5f+OZKQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAK7vlRrGVEePJpW1VHMXdlIwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MTkxOTI4NDNaGA8yMTIxMDUxOTIwMjg0M1owgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhZi1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMZiHOQC6x4o +eC7vVOMCGiN5EuLqPYHdceFPm4h5k/ZejXTf7kryk6aoKZKsDIYihkaZwXVS7Y/y +7Ig1F1ABi2jD+CYprj7WxXbhpysmN+CKG7YC3uE4jSvfvUnpzionkQbjJsRJcrPO +cZJM4FVaVp3mlHHtvnM+K3T+ni4a38nAd8xrv1na4+B8ZzZwWZXarfg8lJoGskSn +ou+3rbGQ0r+XlUP03zWujHoNlVK85qUIQvDfTB7n3O4s1XNGvkfv3GNBhYRWJYlB +4p8T+PFN8wG+UOByp1gV7BD64RnpuZ8V3dRAlO6YVAmINyG5UGrPzkIbLtErUNHO +4iSp4UqYvztDqJWWHR/rA84ef+I9RVwwZ8FQbjKq96OTnPrsr63A5mXTC9dXKtbw +XNJPQY//FEdyM3K8sqM0IdCzxCA1MXZ8+QapWVjwyTjUwFvL69HYky9H8eAER59K +5I7u/CWWeCy2R1SYUBINc3xxLr0CGGukcWPEZW2aPo5ibW5kepU1P/pzdMTaTfao +F42jSFXbc7gplLcSqUgWwzBnn35HLTbiZOFBPKf6vRRu8aRX9atgHw/EjCebi2xP +xIYr5Ub8u0QVHIqcnF1/hVzO/Xz0chj3E6VF/yTXnsakm+W1aM2QkZbFGpga+LMy +mFCtdPrELjea2CfxgibaJX1Q4rdEpc8DAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDSaycEyuspo/NOuzlzblui8KotFMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAbosemjeTRsL9o4v0KadBUNS3V7gdAH+X4vH2 +Ee1Jc91VOGLdd/s1L9UX6bhe37b9WjUD69ur657wDW0RzxMYgQdZ27SUl0tEgGGp +cCmVs1ky3zEN+Hwnhkz+OTmIg1ufq0W2hJgJiluAx2r1ib1GB+YI3Mo3rXSaBYUk +bgQuujYPctf0PA153RkeICE5GI3OaJ7u6j0caYEixBS3PDHt2MJWexITvXGwHWwc +CcrC05RIrTUNOJaetQw8smVKYOfRImEzLLPZ5kf/H3Cbj8BNAFNsa10wgvlPuGOW +XLXqzNXzrG4V3sjQU5YtisDMagwYaN3a6bBf1wFwFIHQoAPIgt8q5zaQ9WI+SBns +Il6rd4zfvjq/BPmt0uI7rVg/cgbaEg/JDL2neuM9CJAzmKxYxLQuHSX2i3Fy4Y1B +cnxnRQETCRZNPGd00ADyxPKVoYBC45/t+yVusArFt+2SVLEGiFBr23eG2CEZu+HS +nDEgIfQ4V3YOTUNa86wvbAss1gbbnT/v1XCnNGClEWCWNCSRjwV2ZmQ/IVTmNHPo +7axTTBBJbKJbKzFndCnuxnDXyytdYRgFU7Ly3sa27WS2KFyFEDebLFRHQEfoYqCu +IupSqBSbXsR3U10OTjc9z6EPo1nuV6bdz+gEDthmxKa1NI+Qb1kvyliXQHL2lfhr +5zT5+Bs= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAOLV6zZcL4IV2xmEneN1GwswDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MDg1OFoYDzIxMjEwNTE5MjAwODU4WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC7koAKGXXlLixN +fVjhuqvz0WxDeTQfhthPK60ekRpftkfE5QtnYGzeovaUAiS58MYVzqnnTACDwcJs +IGTFE6Wd7sB6r8eI/3CwI1pyJfxepubiQNVAQG0zJETOVkoYKe/5KnteKtnEER3X +tCBRdV/rfbxEDG9ZAsYfMl6zzhEWKF88G6xhs2+VZpDqwJNNALvQuzmTx8BNbl5W +RUWGq9CQ9GK9GPF570YPCuURW7kl35skofudE9bhURNz51pNoNtk2Z3aEeRx3ouT +ifFJlzh+xGJRHqBG7nt5NhX8xbg+vw4xHCeq1aAe6aVFJ3Uf9E2HzLB4SfIT9bRp +P7c9c0ySGt+3n+KLSHFf/iQ3E4nft75JdPjeSt0dnyChi1sEKDi0tnWGiXaIg+J+ +r1ZtcHiyYpCB7l29QYMAdD0TjfDwwPayLmq//c20cPmnSzw271VwqjUT0jYdrNAm +gV+JfW9t4ixtE3xF2jaUh/NzL3bAmN5v8+9k/aqPXlU1BgE3uPwMCjrfn7V0I7I1 +WLpHyd9jF3U/Ysci6H6i8YKgaPiOfySimQiDu1idmPld659qerutUSemQWmPD3bE +dcjZolmzS9U0Ujq/jDF1YayN3G3xvry1qWkTci0qMRMu2dZu30Herugh9vsdTYkf +00EqngPbqtIVLDrDjEQLqPcb8QvWFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBQBqg8Za/L0YMHURGExHfvPyfLbOTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBACAGPMa1QL7P/FIO7jEtMelJ0hQlQepKnGtbKz4r +Xq1bUX1jnLvnAieR9KZmeQVuKi3g3CDU6b0mDgygS+FL1KDDcGRCSPh238Ou8KcG +HIxtt3CMwMHMa9gmdcMlR5fJF9vhR0C56KM2zvyelUY51B/HJqHwGvWuexryXUKa +wq1/iK2/d9mNeOcjDvEIj0RCMI8dFQCJv3PRCTC36XS36Tzr6F47TcTw1c3mgKcs +xpcwt7ezrXMUunzHS4qWAA5OGdzhYlcv+P5GW7iAA7TDNrBF+3W4a/6s9v2nQAnX +UvXd9ul0ob71377UhZbJ6SOMY56+I9cJOOfF5QvaL83Sz29Ij1EKYw/s8TYdVqAq ++dCyQZBkMSnDFLVe3J1KH2SUSfm3O98jdPORQrUlORQVYCHPls19l2F6lCmU7ICK +hRt8EVSpXm4sAIA7zcnR2nU00UH8YmMQLnx5ok9YGhuh3Ehk6QlTQLJux6LYLskd +9YHOLGW/t6knVtV78DgPqDeEx/Wu/5A8R0q7HunpWxr8LCPBK6hksZnOoUhhb8IP +vl46Ve5Tv/FlkyYr1RTVjETmg7lb16a8J0At14iLtpZWmwmuv4agss/1iBVMXfFk ++ZGtx5vytWU5XJmsfKA51KLsMQnhrLxb3X3zC+JRCyJoyc8++F3YEcRi2pkRYE3q +Hing +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAI+asxQA/MB1cGyyrC0MPpkwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBjYS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIzMDkxMzIwMjEzNFoYDzIwNjMwOTEzMjEyMTMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDMHvQITTZcfl2O +yfzRIAPKwzzlc8eXWdXef7VUsbezg3lm9RC+vArO4JuAzta/aLw1D94wPSRm9JXX +NkP3obO6Ql80/0doooU6BAPceD0xmEWC4aCFT/5KWsD6Sy2/Rjwq3NKBTwzxLwYK +GqVsBp8AdrzDTmdRETC+Dg2czEo32mTDAA1uMgqrz6xxeTYroj8NTSTp6jfE6C0n +YgzYmVQCEIjHqI49j7k3jfT3P2skCVKGJwQzoZnerFacKzXsDB18uIqU7NaMc2cX +kOd0gRqpyKOzAHU2m5/S4jw4UHdkoI3E7nkayuen8ZPKH2YqWtTXUrXGhSTT34nX +yiFgu+vTAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFHzz1NTd +TOm9zAv4d8l6XCFKSdJfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAodBvd0cvXQYhFBef2evnuI9XA+AC/Q9P1nYtbp5MPA4aFhy5v9rjW8wwJX14 +l+ltd2o3tz8PFDBZ1NX2ooiWVlZthQxKn1/xDVKsTXHbYUXItPQ3jI5IscB5IML8 +oCzAbkoLXsSPNOVFP5P4l4cZEMqHGRnBag7hLJZvmvzZSBnz+ioC2jpjVluF8kDX +fQGNjqPECik68CqbSV0SaQ0cgEoYTDjwON5ZLBeS8sxR2abE/gsj4VFYl5w/uEBd +w3Tt9uGfIy+wd2tNj6isGC6PcbPMjA31jd+ifs2yNzigqkcYTTWFtnvh4a8xiecm +GHu2EgH0Jqzz500N7L3uQdPkdg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRANxgyBbnxgTEOpDul2ZnC0UwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNjEwMTgxOTA3WhgPMjA2MTA2MTAxOTE5MDda +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +xnwSDAChrMkfk5TA4Dk8hKzStDlSlONzmd3fTG0Wqr5+x3EmFT6Ksiu/WIwEl9J2 +K98UI7vYyuZfCxUKb1iMPeBdVGqk0zb92GpURd+Iz/+K1ps9ZLeGBkzR8mBmAi1S +OfpwKiTBzIv6E8twhEn4IUpHsdcuX/2Y78uESpJyM8O5CpkG0JaV9FNEbDkJeBUQ +Ao2qqNcH4R0Qcr5pyeqA9Zto1RswgL06BQMI9dTpfwSP5VvkvcNUaLl7Zv5WzLQE +JzORWePvdPzzvWEkY/3FPjxBypuYwssKaERW0fkPDmPtykktP9W/oJolKUFI6pXp +y+Y6p6/AVdnQD2zZjW5FhQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBT+jEKs96LC+/X4BZkUYUkzPfXdqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIGQqgqcQ6XSGkmNebzR6DhadTbfDmbYeN5N0Vuzv+Tdmufb +tMGjdjnYMg4B+IVnTKQb+Ox3pL9gbX6KglGK8HupobmIRtwKVth+gYYz3m0SL/Nk +haWPYzOm0x3tJm8jSdufJcEob4/ATce9JwseLl76pSWdl5A4lLjnhPPKudUDfH+1 +BLNUi3lxpp6GkC8aWUPtupnhZuXddolTLOuA3GwTZySI44NfaFRm+o83N1jp+EwD +6e94M4cTRzjUv6J3MZmSbdtQP/Tk1uz2K4bQZGP0PZC3bVpqiesdE/xr+wbu8uHr +cM1JXH0AmXf1yIkTgyWzmvt0k1/vgcw5ixAqvvE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEATCCAumgAwIBAgIRAMhw98EQU18mIji+unM2YH8wDQYJKoZIhvcNAQELBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQyMjJaGA8yMDYyMDYwNjIyNDIyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAIeeRoLfTm+7 +vqm7ZlFSx+1/CGYHyYrOOryM4/Z3dqYVHFMgWTR7V3ziO8RZ6yUanrRcWVX3PZbF +AfX0KFE8OgLsXEZIX8odSrq86+/Th5eZOchB2fDBsUB7GuN2rvFBbM8lTI9ivVOU +lbuTnYyb55nOXN7TpmH2bK+z5c1y9RVC5iQsNAl6IJNvSN8VCqXh31eK5MlKB4DT ++Y3OivCrSGsjM+UR59uZmwuFB1h+icE+U0p9Ct3Mjq3MzSX5tQb6ElTNGlfmyGpW +Kh7GQ5XU1KaKNZXoJ37H53woNSlq56bpVrKI4uv7ATpdpFubOnSLtpsKlpLdR3sy +Ws245200pC8CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUp0ki +6+eWvsnBjQhMxwMW5pwn7DgwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUA +A4IBAQB2V8lv0aqbYQpj/bmVv/83QfE4vOxKCJAHv7DQ35cJsTyBdF+8pBczzi3t +3VNL5IUgW6WkyuUOWnE0eqAFOUVj0yTS1jSAtfl3vOOzGJZmWBbqm9BKEdu1D8O6 +sB8bnomwiab2tNDHPmUslpdDqdabbkWwNWzLJ97oGFZ7KNODMEPXWKWNxg33iHfS +/nlmnrTVI3XgaNK9qLZiUrxu9Yz5gxi/1K+sG9/Dajd32ZxjRwDipOLiZbiXQrsd +qzIMY4GcWf3g1gHL5mCTfk7dG22h/rhPyGV0svaDnsb+hOt6sv1McMN6Y3Ou0mtM +/UaAXojREmJmTSCNvs2aBny3/2sy +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMnRxsKLYscJV8Qv5pWbL7swCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTgxNjAxWhgPMjEyMTA1MTkxOTE2MDFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgc2EtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjFOCZgTNVKxLKhUxffiDEvTLFhrmIqdO +dKqVdgDoELEzIHWDdC+19aDPitbCYtBVHl65ITu/9pn6mMUl5hhUNtfZuc6A+Iw1 +sBe0v0qI3y9Q9HdQYrGgeHDh8M5P7E2ho0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBS5L7/8M0TzoBZk39Ps7BkfTB4yJTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwI43O0NtWKTgnVv9z0LO5UMZYgSve7GvGTwqktZYCMObE +rUI4QerXM9D6JwLy09mqAjEAypfkdLyVWtaElVDUyHFkihAS1I1oUxaaDrynLNQK +Ou/Ay+ns+J+GyvyDUjBpVVW1 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQR71Z8lTO5Sj+as2jB7IWXzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjIwMzIwWhgPMjEyMTA1MjQyMzAzMjBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAM977bHIs1WJijrS +XQMfUOhmlJjr2v0K0UjPl52sE1TJ76H8umo1yR4T7Whkd9IwBHNGKXCJtJmMr9zp +fB38eLTu+5ydUAXdFuZpRMKBWwPVe37AdJRKqn5beS8HQjd3JXAgGKUNNuE92iqF +qi2fIqFMpnJXWo0FIW6s2Dl2zkORd7tH0DygcRi7lgVxCsw1BJQhFJon3y+IV8/F +bnbUXSNSDUnDW2EhvWSD8L+t4eiXYsozhDAzhBvojpxhPH9OB7vqFYw5qxFx+G0t +lSLX5iWi1jzzc3XyGnB6WInZDVbvnvJ4BGZ+dTRpOCvsoMIn9bz4EQTvu243c7aU +HbS/kvnCASNt+zk7C6lbmaq0AGNztwNj85Opn2enFciWZVnnJ/4OeefUWQxD0EPp +SjEd9Cn2IHzkBZrHCg+lWZJQBKbUVS0lLIMSsLQQ6WvR38jY7D2nxM1A93xWxwpt +ZtQnYRCVXH6zt2OwDAFePInWwxUjR5t/wu3XxPgpSfrmTi3WYtr1wFypAJ811e/P +yBtswWUQ6BNJQvy+KnOEeGfOwmtdDFYR+GOCfvCihzrKJrxOtHIieehR5Iw3cbXG +sm4pDzfMUVvDDz6C2M6PRlJhhClbatHCjik9hxFYEsAlqtVVK9pxaz9i8hOqSFQq +kJSQsgWw+oM/B2CyjcSqkSQEu8RLAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFPmrdxpRRgu3IcaB5BTqlprcKdTsMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAVdlxWjPvVKky3kn8ZizeM4D+EsLw9dWLau2UD/ls +zwDCFoT6euagVeCknrn+YEl7g20CRYT9iaonGoMUPuMR/cdtPL1W/Rf40PSrGf9q +QuxavWiHLEXOQTCtCaVZMokkvjuuLNDXyZnstgECuiZECTwhexUF4oiuhyGk9o01 +QMaiz4HX4lgk0ozALUvEzaNd9gWEwD2qe+rq9cQMTVq3IArUkvTIftZUaVUMzr0O +ed1+zAsNa9nJhURJ/6anJPJjbQgb5qA1asFcp9UaMT1ku36U3gnR1T/BdgG2jX3X +Um0UcaGNVPrH1ukInWW743pxWQb7/2sumEEMVh+jWbB18SAyLI4WIh4lkurdifzS +IuTFp8TEx+MouISFhz/vJDWZ84tqoLVjkEcP6oDypq9lFoEzHDJv3V1CYcIgOusT +k1jm9P7BXdTG7TYzUaTb9USb6bkqkD9EwJAOSs7DI94aE6rsSws2yAHavjAMfuMZ +sDAZvkqS2Qg2Z2+CI6wUZn7mzkJXbZoqRjDvChDXEB1mIhzVXhiNW/CR5WKVDvlj +9v1sdGByh2pbxcLQtVaq/5coM4ANgphoNz3pOYUPWHS+JUrIivBZ+JobjXcxr3SN +9iDzcu5/FVVNbq7+KN/nvPMngT+gduEN5m+EBjm8GukJymFG0m6BENRA0QSDqZ7k +zDY= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAK5EYG3iHserxMqgg+0EFjgwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI0MjAyMzE2WhgPMjA2MTA1MjQyMTIzMTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +s1L6TtB84LGraLHVC+rGPhLBW2P0oN/91Rq3AnYwqDOuTom7agANwEjvLq7dSRG/ +sIfZsSV/ABTgArZ5sCmLjHFZAo8Kd45yA9byx20RcYtAG8IZl+q1Cri+s0XefzyO +U6mlfXZkVe6lzjlfXBkrlE/+5ifVbJK4dqOS1t9cWIpgKqv5fbE6Qbq4LVT+5/WM +Vd2BOljuBMGMzdZubqFKFq4mzTuIYfnBm7SmHlZfTdfBYPP1ScNuhpjuzw4n3NCR +EdU6dQv04Q6th4r7eiOCwbWI9LkmVbvBe3ylhH63lApC7MiiPYLlB13xBubVHVhV +q1NHoNTi+zA3MN9HWicRxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSuxoqm0/wjNiZLvqv+JlQwsDvTPDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAFfTK/j5kv90uIbM8VaFdVbr/6weKTwehafT0pAk1bfLVX+7 +uf8oHgYiyKTTl0DFQicXejghXTeyzwoEkWSR8c6XkhD5vYG3oESqmt/RGvvoxz11 +rHHy7yHYu7RIUc3VQG60c4qxXv/1mWySGwVwJrnuyNT9KZXPevu3jVaWOVHEILaK +HvzQ2YEcWBPmde/zEseO2QeeGF8FL45Q1d66wqIP4nNUd2pCjeTS5SpB0MMx7yi9 +ki1OH1pv8tOuIdimtZ7wkdB8+JSZoaJ81b8sRrydRwJyvB88rftuI3YB4WwGuONT +ZezUPsmaoK69B0RChB0ofDpAaviF9V3xOWvVZfo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGDzCCA/egAwIBAgIRAI0sMNG2XhaBMRN3zD7ZyoEwDQYJKoZIhvcNAQEMBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA1NzUwWhgPMjEyMTA1MTgyMTU3 +NTBaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC +CgKCAgEAh/otSiCu4Uw3hu7OJm0PKgLsLRqBmUS6jihcrkxfN2SHmp2zuRflkweU +BhMkebzL+xnNvC8okzbgPWtUxSmDnIRhE8J7bvSKFlqs/tmEdiI/LMqe/YIKcdsI +20UYmvyLIjtDaJIh598SHHlF9P8DB5jD8snJfhxWY+9AZRN+YVTltgQAAgayxkWp +M1BbvxpOnz4CC00rE0eqkguXIUSuobb1vKqdKIenlYBNxm2AmtgvQfpsBIQ0SB+8 +8Zip8Ef5rtjSw5J3s2Rq0aYvZPfCVIsKYepIboVwXtD7E9J31UkB5onLBQlaHaA6 +XlH4srsMmrew5d2XejQGy/lGZ1nVWNsKO0x/Az2QzY5Kjd6AlXZ8kq6H68hscA5i +OMbNlXzeEQsZH0YkId3+UsEns35AAjZv4qfFoLOu8vDotWhgVNT5DfdbIWZW3ZL8 +qbmra3JnCHuaTwXMnc25QeKgVq7/rG00YB69tCIDwcf1P+tFJWxvaGtV0g2NthtB +a+Xo09eC0L53gfZZ3hZw1pa3SIF5dIZ6RFRUQ+lFOux3Q/I3u+rYstYw7Zxc4Zeo +Y8JiedpQXEAnbw2ECHix/L6mVWgiWCiDzBnNLLdbmXjJRnafNSndSfFtHCnY1SiP +aCrNpzwZIJejoV1zDlWAMO+gyS28EqzuIq3WJK/TFE7acHkdKIcCAwEAAaNCMEAw +DwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUrmV1YASnuudfmqAZP4sKGTvScaEw +DgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBGpEKeQoPvE85tN/25 +qHFkys9oHDl93DZ62EnOqAUKLd6v0JpCyEiop4nlrJe+4KrBYVBPyKOJDcIqE2Sp +3cvgJXLhY4i46VM3Qxe8yuYF1ElqBpg3jJVj/sCQnYz9dwoAMWIJFaDWOvmU2E7M +MRaKx+sPXFkIjiDA6Bv0m+VHef7aedSYIY7IDltEQHuXoqNacGrYo3I50R+fZs88 +/mB3e/V7967e99D6565yf9Lcjw4oQf2Hy7kl/6P9AuMz0LODnGITwh2TKk/Zo3RU +Vgq25RDrT4xJK6nFHyjUF6+4cOBxVpimmFw/VP1zaXT8DN5r4HyJ9p4YuSK8ha5N +2pJc/exvU8Nv2+vS/efcDZWyuEdZ7eh1IJWQZlOZKIAONfRDRTpeQHJ3zzv3QVYy +t78pYp/eWBHyVIfEE8p2lFKD4279WYe+Uvdb8c4Jm4TJwqkSJV8ifID7Ub80Lsir +lPAU3OCVTBeVRFPXT2zpC4PB4W6KBSuj6OOcEu2y/HgWcoi7Cnjvp0vFTUhDFdus +Wz3ucmJjfVsrkEO6avDKu4SwdbVHsk30TVAwPd6srIdi9U6MOeOQSOSE4EsrrS7l +SVmu2QIDUVFpm8QAHYplkyWIyGkupyl3ashH9mokQhixIU/Pzir0byePxHLHrwLu +1axqeKpI0F5SBUPsaVNYY2uNFg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECDCCAvCgAwIBAgIQCREfzzVyDTMcNME+gWnTCTANBgkqhkiG9w0BAQsFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQyMzNaGA8yMDYxMDUyNDIxNDIzM1ow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDL +1MT6br3L/4Pq87DPXtcjlXN3cnbNk2YqRAZHJayStTz8VtsFcGPJOpk14geRVeVk +e9uKFHRbcyr/RM4owrJTj5X4qcEuATYZbo6ou/rW2kYzuWFZpFp7lqm0vasV4Z9F +fChlhwkNks0UbM3G+psCSMNSoF19ERunj7w2c4E62LwujkeYLvKGNepjnaH10TJL +2krpERd+ZQ4jIpObtRcMH++bTrvklc+ei8W9lqrVOJL+89v2piN3Ecdd389uphst +qQdb1BBVXbhUrtuGHgVf7zKqN1SkCoktoWxVuOprVWhSvr7akaWeq0UmlvbEsujU +vADqxGMcJFyCzxx3CkJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFFk8UJmlhoxFT3PP12PvhvazHjT4MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG +9w0BAQsFAAOCAQEAfFtr2lGoWVXmWAsIo2NYre7kzL8Xb9Tx7desKxCCz5HOOvIr +8JMB1YK6A7IOvQsLJQ/f1UnKRh3X3mJZjKIywfrMSh0FiDf+rjcEzXxw2dGtUem4 +A+WMvIA3jwxnJ90OQj5rQ8bg3iPtE6eojzo9vWQGw/Vu48Dtw1DJo9210Lq/6hze +hPhNkFh8fMXNT7Q1Wz/TJqJElyAQGNOXhyGpHKeb0jHMMhsy5UNoW5hLeMS5ffao +TBFWEJ1gVfxIU9QRxSh+62m46JIg+dwDlWv8Aww14KgepspRbMqDuaM2cinoejv6 +t3dyOyHHrsOyv3ffZUKtQhQbQr+sUcL89lARsg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAIJLTMpzGNxqHZ4t+c1MlCIwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBhcC1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIxMzAzM1oYDzIwNjEwNTI1MjIzMDMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDtdHut0ZhJ9Nn2 +MpVafFcwHdoEzx06okmmhjJsNy4l9QYVeh0UUoek0SufRNMRF4d5ibzpgZol0Y92 +/qKWNe0jNxhEj6sXyHsHPeYtNBPuDMzThfbvsLK8z7pBP7vVyGPGuppqW/6m4ZBB +lcc9fsf7xpZ689iSgoyjiT6J5wlVgmCx8hFYc/uvcRtfd8jAHvheug7QJ3zZmIye +V4htOW+fRVWnBjf40Q+7uTv790UAqs0Zboj4Yil+hER0ibG62y1g71XcCyvcVpto +2/XW7Y9NCgMNqQ7fGN3wR1gjtSYPd7DO32LTzYhutyvfbpAZjsAHnoObmoljcgXI +QjfBcCFpAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJI3aWLg +CS5xqU5WYVaeT5s8lpO0MA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAUwATpJOcGVOs3hZAgJwznWOoTzOVJKfrqBum7lvkVH1vBwxBl9CahaKj3ZOt +YYp2qJzhDUWludL164DL4ZjS6eRedLRviyy5cRy0581l1MxPWTThs27z+lCC14RL +PJZNVYYdl7Jy9Q5NsQ0RBINUKYlRY6OqGDySWyuMPgno2GPbE8aynMdKP+f6G/uE +YHOf08gFDqTsbyfa70ztgVEJaRooVf5JJq4UQtpDvVswW2reT96qi6tXPKHN5qp3 +3wI0I1Mp4ePmiBKku2dwYzPfrJK/pQlvu0Gu5lKOQ65QdotwLAAoaFqrf9za1yYs +INUkHLWIxDds+4OHNYcerGp5Dw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAIO6ldra1KZvNWJ0TA1ihXEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjE0NTA1WhgPMjEyMTA1MjEyMjQ1MDVa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +sDN52Si9pFSyZ1ruh3xAN0nVqEs960o2IK5CPu/ZfshFmzAwnx/MM8EHt/jMeZtj +SM58LADAsNDL01ELpFZATjgZQ6xNAyXRXE7RiTRUvNkK7O3o2qAGbLnJq/UqF7Sw +LRnB8V6hYOv+2EjVnohtGCn9SUFGZtYDjWXsLd4ML4Zpxv0a5LK7oEC7AHzbUR7R +jsjkrXqSv7GE7bvhSOhMkmgxgj1F3J0b0jdQdtyyj109aO0ATUmIvf+Bzadg5AI2 +A9UA+TUcGeebhpHu8AP1Hf56XIlzPpaQv3ZJ4vzoLaVNUC7XKzAl1dlvCl7Klg/C +84qmbD/tjZ6GHtzpLKgg7kQEV7mRoXq8X4wDX2AFPPQl2fv+Kbe+JODqm5ZjGegm +uskABBi8IFv1hYx9jEulZPxC6uD/09W2+niFm3pirnlWS83BwVDTUBzF+CooUIMT +jhWkIIZGDDgMJTzouBHfoSJtS1KpUZi99m2WyVs21MNKHeWAbs+zmI6TO5iiMC+T +uB8spaOiHFO1573Fmeer4sy3YA6qVoqVl6jjTQqOdy3frAMbCkwH22/crV8YA+08 +hLeHXrMK+6XUvU+EtHAM3VzcrLbuYJUI2XJbzTj5g0Eb8I8JWsHvWHR5K7Z7gceR +78AzxQmoGEfV6KABNWKsgoCQnfb1BidDJIe3BsI0A6UCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUABp0MlB14MSHgAcuNSOhs3MOlUcwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCv4CIOBSQi/QR9NxdRgVAG/pAh +tFJhV7OWb/wqwsNKFDtg6tTxwaahdCfWpGWId15OUe7G9LoPiKiwM9C92n0ZeHRz +4ewbrQVo7Eu1JI1wf0rnZJISL72hVYKmlvaWaacHhWxvsbKLrB7vt6Cknxa+S993 +Kf8i2Psw8j5886gaxhiUtzMTBwoDWak8ZaK7m3Y6C6hXQk08+3pnIornVSFJ9dlS +PAqt5UPwWmrEfF+0uIDORlT+cvrAwgSp7nUF1q8iasledycZ/BxFgQqzNwnkBDwQ +Z/aM52ArGsTzfMhkZRz9HIEhz1/0mJw8gZtDVQroD8778h8zsx2SrIz7eWQ6uWsD +QEeSWXpcheiUtEfzkDImjr2DLbwbA23c9LoexUD10nwohhoiQQg77LmvBVxeu7WU +E63JqaYUlOLOzEmNJp85zekIgR8UTkO7Gc+5BD7P4noYscI7pPOL5rP7YLg15ZFi +ega+G53NTckRXz4metsd8XFWloDjZJJq4FfD60VuxgXzoMNT9wpFTNSH42PR2s9L +I1vcl3w8yNccs9se2utM2nLsItZ3J0m/+QSRiw9hbrTYTcM9sXki0DtH2kyIOwYf +lOrGJDiYOIrXSQK36H0gQ+8omlrUTvUj4msvkXuQjlfgx6sgp2duOAfnGxE7uHnc +UhnJzzoe6M+LfGHkVQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQSAG6j2WHtWUUuLGJTPb1nTAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2MzgyNloYDzIxMjEwNTIwMTczODI2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2eqwU4FOzW8RV1W381Bd +olhDOrqoMqzWli21oDUt7y8OnXM/lmAuOS6sr8Nt61BLVbONdbr+jgCYw75KabrK +ZGg3siqvMOgabIKkKuXO14wtrGyGDt7dnKXg5ERGYOZlo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBS1Acp2WYxOcblv5ikZ3ZIbRCCW+zAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAJL84J08PBprxmsAKPTotBuVI3MyW1r8 +xQ0i8lgCQUf8GcmYjQ0jI4oZyv+TuYJAcwIxAP9Xpzq0Docxb+4N1qVhpiOfWt1O +FnemFiy9m1l+wv6p3riQMPV7mBVpklmijkIv3Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRALZLcqCVIJ25maDPE3sbPCIwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIxMjEzOTM5WhgPMjA2MTA1MjEyMjM5Mzla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +ypKc+6FfGx6Gl6fQ78WYS29QoKgQiur58oxR3zltWeg5fqh9Z85K5S3UbRSTqWWu +Xcfnkz0/FS07qHX+nWAGU27JiQb4YYqhjZNOAq8q0+ptFHJ6V7lyOqXBq5xOzO8f ++0DlbJSsy7GEtJp7d7QCM3M5KVY9dENVZUKeJwa8PC5StvwPx4jcLeZRJC2rAVDG +SW7NAInbATvr9ssSh03JqjXb+HDyywiqoQ7EVLtmtXWimX+0b3/2vhqcH5jgcKC9 +IGFydrjPbv4kwMrKnm6XlPZ9L0/3FMzanXPGd64LQVy51SI4d5Xymn0Mw2kMX8s6 +Nf05OsWcDzJ1n6/Q1qHSxQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRmaIc8eNwGP7i6P7AJrNQuK6OpFzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIBeHfGwz3S2zwIUIpqEEI5/sMySDeS+3nJR+woWAHeO0C8i +BJdDh+kzzkP0JkWpr/4NWz84/IdYo1lqASd1Kopz9aT1+iROXaWr43CtbzjXb7/X +Zv7eZZFC8/lS5SROq42pPWl4ekbR0w8XGQElmHYcWS41LBfKeHCUwv83ATF0XQ6I +4t+9YSqZHzj4vvedrvcRInzmwWJaal9s7Z6GuwTGmnMsN3LkhZ+/GD6oW3pU/Pyh +EtWqffjsLhfcdCs3gG8x9BbkcJPH5aPAVkPn4wc8wuXg6xxb9YGsQuY930GWTYRf +schbgjsuqznW4HHakq4WNhs1UdTSTKkRdZz7FUQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEDzCCAvegAwIBAgIRAM2zAbhyckaqRim63b+Tib8wDQYJKoZIhvcNAQELBQAw +gZ8xCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE4MDYGA1UEAwwv +QW1hem9uIFJEUyBQcmV2aWV3IHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzEx +EDAOBgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjA0OTQ1WhgPMjA2MTA1MTgyMTQ5 +NDVaMIGfMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNl +cywgSW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExODA2BgNV +BAMML0FtYXpvbiBSRFMgUHJldmlldyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4 +IEcxMRAwDgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA1ybjQMH1MkbvfKsWJaCTXeCSN1SG5UYid+Twe+TjuSqaXWonyp4WRR5z +tlkqq+L2MWUeQQAX3S17ivo/t84mpZ3Rla0cx39SJtP3BiA2BwfUKRjhPwOjmk7j +3zrcJjV5k1vSeLNOfFFSlwyDiVyLAE61lO6onBx+cRjelu0egMGq6WyFVidTdCmT +Q9Zw3W6LTrnPvPmEyjHy2yCHzH3E50KSd/5k4MliV4QTujnxYexI2eR8F8YQC4m3 +DYjXt/MicbqA366SOoJA50JbgpuVv62+LSBu56FpzY12wubmDZsdn4lsfYKiWxUy +uc83a2fRXsJZ1d3whxrl20VFtLFHFQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRC0ytKmDYbfz0Bz0Psd4lRQV3aNTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQELBQADggEBAGv8qZu4uaeoF6zsbumauz6ea6tdcWt+hGFuwGrb +tRbI85ucAmVSX06x59DJClsb4MPhL1XmqO3RxVMIVVfRwRHWOsZQPnXm8OYQ2sny +rYuFln1COOz1U/KflZjgJmxbn8x4lYiTPZRLarG0V/OsCmnLkQLPtEl/spMu8Un7 +r3K8SkbWN80gg17Q8EV5mnFwycUx9xsTAaFItuG0en9bGsMgMmy+ZsDmTRbL+lcX +Fq8r4LT4QjrFz0shrzCwuuM4GmcYtBSxlacl+HxYEtAs5k10tmzRf6OYlY33tGf6 +1tkYvKryxDPF/EDgGp/LiBwx6ixYMBfISoYASt4V/ylAlHA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtTCCAjqgAwIBAgIRAK9BSZU6nIe6jqfODmuVctYwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjIxMzA5WhgPMjEyMTA1MjEyMzEzMDlaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgY2EtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEUkEERcgxneT5H+P+fERcbGmf +bVx+M7rNWtgWUr6w+OBENebQA9ozTkeSg4c4M+qdYSObFqjxITdYxT1z/nHz1gyx +OKAhLjWu+nkbRefqy3RwXaWT680uUaAP6ccnkZOMo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBSN6fxlg0s5Wny08uRBYZcQ3TUoyzAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaQAwZgIxAORaz+MBVoFBTmZ93j2G2vYTwA6T5hWzBWrx +CrI54pKn5g6At56DBrkjrwZF5T1enAIxAJe/LZ9xpDkAdxDgGJFN8gZYLRWc0NRy +Rb4hihy5vj9L+w9uKc9VfEBIFuhT7Z3ljg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQB/57HSuaqUkLaasdjxUdPjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE3NDAzNFoYDzIwNjEwNTE5MTg0MDM0WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtbkaoVsUS76o +TgLFmcnaB8cswBk1M3Bf4IVRcwWT3a1HeJSnaJUqWHCJ+u3ip/zGVOYl0gN1MgBb +MuQRIJiB95zGVcIa6HZtx00VezDTr3jgGWRHmRjNVCCHGmxOZWvJjsIE1xavT/1j +QYV/ph4EZEIZ/qPq7e3rHohJaHDe23Z7QM9kbyqp2hANG2JtU/iUhCxqgqUHNozV +Zd0l5K6KnltZQoBhhekKgyiHqdTrH8fWajYl5seD71bs0Axowb+Oh0rwmrws3Db2 +Dh+oc2PwREnjHeca9/1C6J2vhY+V0LGaJmnnIuOANrslx2+bgMlyhf9j0Bv8AwSi +dSWsobOhNQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQb7vJT +VciLN72yJGhaRKLn6Krn2TAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAAxEj8N9GslReAQnNOBpGl8SLgCMTejQ6AW/bapQvzxrZrfVOZOYwp/5oV0f +9S1jcGysDM+DrmfUJNzWxq2Y586R94WtpH4UpJDGqZp+FuOVJL313te4609kopzO +lDdmd+8z61+0Au93wB1rMiEfnIMkOEyt7D2eTFJfJRKNmnPrd8RjimRDlFgcLWJA +3E8wca67Lz/G0eAeLhRHIXv429y8RRXDtKNNz0wA2RwURWIxyPjn1fHjA9SPDkeW +E1Bq7gZj+tBnrqz+ra3yjZ2blss6Ds3/uRY6NYqseFTZWmQWT7FolZEnT9vMUitW +I0VynUbShVpGf6946e0vgaaKw20= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQGyUVTaVjYJvWhroVEiHPpDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTE5MTkwNDA2WhgPMjA2MTA1MTkyMDA0MDZaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhyXpJ0t4nigRDZ +EwNtFOem1rM1k8k5XmziHKDvDk831p7QsX9ZOxl/BT59Pu/P+6W6SvasIyKls1sW +FJIjFF+6xRQcpoE5L5evMgN/JXahpKGeQJPOX9UEXVW5B8yi+/dyUitFT7YK5LZA +MqWBN/LtHVPa8UmE88RCDLiKkqiv229tmwZtWT7nlMTTCqiAHMFcryZHx0pf9VPh +x/iPV8p2gBJnuPwcz7z1kRKNmJ8/cWaY+9w4q7AYlAMaq/rzEqDaN2XXevdpsYAK +TMMj2kji4x1oZO50+VPNfBl5ZgJc92qz1ocF95SAwMfOUsP8AIRZkf0CILJYlgzk +/6u6qZECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUm5jfcS9o ++LwL517HpB6hG+PmpBswDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAcQ6lsqxi63MtpGk9XK8mCxGRLCad51+MF6gcNz6i6PAqhPOoKCoFqdj4cEQTF +F8dCfa3pvfJhxV6RIh+t5FCk/y6bWT8Ls/fYKVo6FhHj57bcemWsw/Z0XnROdVfK +Yqbc7zvjCPmwPHEqYBhjU34NcY4UF9yPmlLOL8uO1JKXa3CAR0htIoW4Pbmo6sA4 +6P0co/clW+3zzsQ92yUCjYmRNeSbdXbPfz3K/RtFfZ8jMtriRGuO7KNxp8MqrUho +HK8O0mlSUxGXBZMNicfo7qY8FD21GIPH9w5fp5oiAl7lqFzt3E3sCLD3IiVJmxbf +fUwpGd1XZBBSdIxysRLM6j48 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrTCCAjOgAwIBAgIQU+PAILXGkpoTcpF200VD/jAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGFwLWVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMTQ1MTFaGA8yMTIxMDUyNTIyNDUxMVowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBhcC1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAT3tFKE8Kw1sGQAvNLlLhd8OcGhlc7MiW/s +NXm3pOiCT4vZpawKvHBzD76Kcv+ZZzHRxQEmG1/muDzZGlKR32h8AAj+NNO2Wy3d +CKTtYMiVF6Z2zjtuSkZQdjuQbe4eQ7qjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFAiSQOp16Vv0Ohpvqcbd2j5RmhYNMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNoADBlAjBVsi+5Ape0kOhMt/WFkANkslD4qXA5uqhrfAtH29Xzz2NV +tR7akiA771OaIGB/6xsCMQCZt2egCtbX7J0WkuZ2KivTh66jecJr5DHvAP4X2xtS +F/5pS+AUhcKTEGjI9jDH3ew= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQT5mGlavQzFHsB7hV6Mmy6TAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTAxNVoYDzIxMjEwNTI0MjE1MDE1WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEcm4BBBjYK7clwm0HJRWS +flt3iYwoJbIXiXn9c1y3E+Vb7bmuyKhS4eO8mwO4GefUcXObRfoHY2TZLhMJLVBQ +7MN2xDc0RtZNj07BbGD3VAIFRTDX0mH9UNYd0JQM3t/Oo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBRrd5ITedfAwrGo4FA9UaDaGFK3rjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAPBNqmVv1IIA3EZyQ6XuVf4gj79/DMO8 +bkicNS1EcBpUqbSuU4Zwt2BYc8c/t7KVOQIxAOHoWkoKZPiKyCxfMtJpCZySUG+n +sXgB/LOyWE5BJcXUfm+T1ckeNoWeUUMOLmnJjg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJcDeinvdNrDQBeJ8+t38WQwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY0OTE2WhgPMjA2MjA1MjUxNzQ5MTZa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +k8DBNkr9tMoIM0NHoFiO7cQfSX0cOMhEuk/CHt0fFx95IBytx7GHCnNzpM27O5z6 +x6iRhfNnx+B6CrGyCzOjxvPizneY+h+9zfvNz9jj7L1I2uYMuiNyOKR6FkHR46CT +1CiArfVLLPaTqgD/rQjS0GL2sLHS/0dmYipzynnZcs613XT0rAWdYDYgxDq7r/Yi +Xge5AkWQFkMUq3nOYDLCyGGfQqWKkwv6lZUHLCDKf+Y0Uvsrj8YGCI1O8mF0qPCQ +lmlfaDvbuBu1AV+aabmkvyFj3b8KRIlNLEtQ4N8KGYR2Jdb82S4YUGIOAt4wuuFt +1B7AUDLk3V/u+HTWiwfoLQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBSNpcjz6ArWBtAA+Gz6kyyZxrrgdDAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGJEd7UgOzHYIcQRSF7nSYyjLROyalaIV9AX4WXW/Cqlul1c +MblP5etDZm7A/thliZIWAuyqv2bNicmS3xKvNy6/QYi1YgxZyy/qwJ3NdFl067W0 +t8nGo29B+EVK94IPjzFHWShuoktIgp+dmpijB7wkTIk8SmIoe9yuY4+hzgqk+bo4 +ms2SOXSN1DoQ75Xv+YmztbnZM8MuWhL1T7hA4AMorzTQLJ9Pof8SpSdMHeDsHp0R +01jogNFkwy25nw7cL62nufSuH2fPYGWXyNDg+y42wKsKWYXLRgUQuDVEJ2OmTFMB +T0Vf7VuNijfIA9hkN2d3K53m/9z5WjGPSdOjGhg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQRiwspKyrO0xoxDgSkqLZczANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI0MjE1OTAwWhgPMjA2MTA1MjQyMjU5MDBaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL53Jk3GsKiu+4bx +jDfsevWbwPCNJ3H08Zp7GWhvI3Tgi39opfHYv2ku2BKFjK8N2L6RvNPSR8yplv5j +Y0tK0U+XVNl8o0ibhqRDhbTuh6KL8CFINWYzAajuxFS+CF0U6c1Q3tXLBdALxA7l +FlXJ71QrP06W31kRe7kvgrvO7qWU3/OzUf9qYw4LSiR1/VkvvRCTqcVNw09clw/M +Jbw6FSgweN65M9j7zPbjGAXSHkXyxH1Erin2fa+B9PE4ZDgX9cp2C1DHewYJQL/g +SepwwcudVNRN1ibKH7kpMrgPnaNIVNx5sXVsTjk6q2ZqYw3SVHegltJpLy/cZReP +mlivF2kCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUmTcQd6o1 +CuS65MjBrMwQ9JJjmBwwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQAKSDSIzl956wVddPThf2VAzI8syw9ngSwsEHZvxVGHBvu5gg618rDyguVCYX9L +4Kw/xJrk6S3qxOS2ZDyBcOpsrBskgahDFIunzoRP3a18ARQVq55LVgfwSDQiunch +Bd05cnFGLoiLkR5rrkgYaP2ftn3gRBRaf0y0S3JXZ2XB3sMZxGxavYq9mfiEcwB0 +LMTMQ1NYzahIeG6Jm3LqRqR8HkzP/Ztq4dT2AtSLvFebbNMiWqeqT7OcYp94HTYT +zqrtaVdUg9bwyAUCDgy0GV9RHDIdNAOInU/4LEETovrtuBU7Z1q4tcHXvN6Hd1H8 +gMb0mCG5I393qW5hFsA/diFb +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAPQAvihfjBg/JDbj6U64K98wDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYyODQxWhgPMjA2MTA1MjAxNzI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +vJ9lgyksCxkBlY40qOzI1TCj/Q0FVGuPL/Z1Mw2YN0l+41BDv0FHApjTUkIKOeIP +nwDwpXTa3NjYbk3cOZ/fpH2rYJ++Fte6PNDGPgKppVCUh6x3jiVZ1L7wOgnTdK1Q +Trw8440IDS5eLykRHvz8OmwvYDl0iIrt832V0QyOlHTGt6ZJ/aTQKl12Fy3QBLv7 +stClPzvHTrgWqVU6uidSYoDtzHbU7Vda7YH0wD9IUoMBf7Tu0rqcE4uH47s2XYkc +SdLEoOg/Ngs7Y9B1y1GCyj3Ux7hnyvCoRTw014QyNB7dTatFMDvYlrRDGG14KeiU +UL7Vo/+EejWI31eXNLw84wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBQkgTWFsNg6wA3HbbihDQ4vpt1E2zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAGz1Asiw7hn5WYUj8RpOCzpE0h/oBZcnxP8wulzZ5Xd0YxWO +0jYUcUk3tTQy1QvoY+Q5aCjg6vFv+oFBAxkib/SmZzp4xLisZIGlzpJQuAgRkwWA +6BVMgRS+AaOMQ6wKPgz1x4v6T0cIELZEPq3piGxvvqkcLZKdCaeC3wCS6sxuafzZ +4qA3zMwWuLOzRftgX2hQto7d/2YkRXga7jSvQl3id/EI+xrYoH6zIWgjdU1AUaNq +NGT7DIo47vVMfnd9HFZNhREsd4GJE83I+JhTqIxiKPNxrKgESzyADmNPt0gXDnHo +tbV1pMZz5HpJtjnP/qVZhEK5oB0tqlKPv9yx074= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuTCCAj6gAwIBAgIRAKp1Rn3aL/g/6oiHVIXtCq8wCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMDMyMTdaGA8yMTIxMDUyNDIxMzIxN1owgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1ub3J0aGVhc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABGTYWPILeBJXfcL3Dz4z +EWMUq78xB1HpjBwHoTURYfcMd5r96BTVG6yaUBWnAVCMeeD6yTG9a1eVGNhG14Hk +ZAEjgLiNB7RRbEG5JZ/XV7W/vODh09WCst2y9SLKsdgeAaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUoE0qZHmDCDB+Bnm8GUa/evpfPwgwDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYCMQCnil5MMwhY3qoXv0xvcKZGxGPaBV15 +0CCssCKn0oVtdJQfJQ3Jrf3RSaEyijXIJsoCMQC35iJi4cWoNX3N/qfgnHohW52O +B5dg0DYMqy5cNZ40+UcAanRMyqNQ6P7fy3umGco= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQPXnDTPegvJrI98qz8WxrMjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxODIxNDAxMloYDzIxMjEwNTE4MjI0MDEyWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEI0sR7gwutK5AB46hM761 +gcLTGBIYlURSEoM1jcBwy56CL+3CJKZwLLyJ7qoOKfWbu5GsVLUTWS8MV6Nw33cx +2KQD2svb694wi+Px2f4n9+XHkEFQw8BbiodDD7RZA70fo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTQSioOvnVLEMXwNSDg+zgln/vAkjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAMwu1hqm5Bc98uE/E0B5iMYbBQ4kpMxO +tP8FTfz5UR37HUn26nXE0puj6S/Ffj4oJgIwXI7s2c26tFQeqzq6u3lrNJHp5jC9 +Uxlo/hEJOLoDj5jnpxo8dMAtCNoQPaHdfL0P +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQEM1pS+bWfBJeu/6j1yIIFzANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGNhLXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjMwOTE5MjIwMTM5WhgPMjEyMzA5MTkyMzAxMzlaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgY2Etd2VzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK2Pyp8p5z6HnlGB +daOj78gZ3ABufxnBFiu5NdFiGoMrS+eY//xxr2iKbnynJAzjmn5A6VKMNxtbuYIZ +WKAzDb/HrWlIYD2w7ZVBXpylfPhiz3jLNsl03WdPNnEruCcivhY2QMewEVtzjPU0 +ofdbZlO2KpF3biv1gjPuIuE7AUyQAbWnWTlrzETAVWLboJJRRqxASSkFUHNLXod7 +ow02FwlAhcnCp9gSe1SKRDrpvvEvYQBAFB7owfnoQzOGDdd87RGyYfyuW8aFI2Z0 +LHNvsA0dTafO4Rh986c72kDL7ijICQdr5OTgZR2OnuESLk1DSK4xYJ4fA6jb5dJ5 ++xsI6tCPykWCW98aO/pha35OsrVNifL/5cH5pdv/ecgQGdffJB+Vdj6f/ZMwR6s/ +Rm37cQ9l3tU8eu/qpzsFjLq1ZUzDaVDWgMW9t49+q/zjhdmbPOabZDao7nHXrVRw +rwPHWCmEY4OmH6ikEKQW3AChFjOdSg4me/J0Jr5l5jKggLPHWbNLRO8qTTK6N8qk +ui3aJDi+XQfsTPARXIw4UFErArNImTsoZVyqfX7I4shp0qZbEhP6kRAbfPljw5kW +Yat7ZlXqDanjsreqbLTaOU10P0rC0/4Ctv5cLSKCrzRLWtpXxhKa2wJTQ74G6fAZ +1oUA79qg3F8nyM+ZzDsfNI854+PNAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLRWiDabEQZNkzEPUCr1ZVJV6xpwMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEATkVVzkkGBjEtLGDtERi+fSpIV0MxwAsA4PAeBBmb +myxo90jz6kWkKM1Wm4BkZM8/mq5VbxPef1kxHfb5CHksCL6SgG5KujfIvht+KT2a +MRJB+III3CbcTy0HtwCX5AlPIbXWydhQFoJTW/OkpecUWoyFM6SqYeYZx1itJpxl +sXshLjYOvw+QgvxRsDxqUfkcaC/N2yhu/30Zo2P8msJfAFry2UmA/TBrWOQKVQxl +Ee/yWgp4U/bC/GZnjWnWDTwkRFGQtI4wjxbVuX6V4FTLCT7kIoHBhG+zOSduJRn3 +Axej7gkEXEVc/PAnwp/kSJ/b0/JONLWdjGUFkyiMn1yJlhJ2sg39vepBN5r6yVYU +nJWoZAuupRpoIKfmC3/cZanXqYbYl4yxzX/PMB4kAACfdxGxLawjnnBjSzaWokXs +YVh2TjWpUMwLOi0RB2mtPUjHdDLKtjOTZ1zHZnR/wVp9BmVI1BXYnz5PAqU5XqeD +EmanyaAuFCeyol1EtbQhgtysThQ+vwYAXMm2iKzJxq0hik8wyG8X55FhnGEOGV3u +xxq7odd3/8BXkc3dGdBPQtH+k5glaQyPnAsLVAIUvyzTmy58saL+nJnQY4mmRrwV +1jJA7nnkaklI/L5fvfCg0W+TMinCOAGd+GQ4hK2SAsJLtcqiBgPf2wJHO8wiwUh9 +Luw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQGKVv+5VuzEZEBzJ+bVfx2zAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTc1MDU5WhgPMjEyMTA1MTkxODUwNTlaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABMqdLJ0tZF/DGFZTKZDrGRJZID8ivC2I +JRCYTWweZKCKSCAzoiuGGHzJhr5RlLHQf/QgmFcgXsdmO2n3CggzhA4tOD9Ip7Lk +P05eHd2UPInyPCHRgmGjGb0Z+RdQ6zkitKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUC1yhRgVqU5bR8cGzOUCIxRpl4EYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMG0c/zLGECRPzGKJvYCkpFTCUvdP4J74YP0v/dPvKojL +t/BrR1Tg4xlfhaib7hPc7wIwFvgqHes20CubQnZmswbTKLUrgSUW4/lcKFpouFd2 +t2/ewfi/0VhkeUW+IiHhOMdU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOXxJuyXVkbfhZCkS/dOpfEwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1OTEwWhgPMjEyMTA1MjUyMjU5MTBa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +xiP4RDYm4tIS12hGgn1csfO8onQDmK5SZDswUpl0HIKXOUVVWkHNlINkVxbdqpqH +FhbyZmNN6F/EWopotMDKe1B+NLrjNQf4zefv2vyKvPHJXhxoKmfyuTd5Wk8k1F7I +lNwLQzznB+ElhrLIDJl9Ro8t31YBBNFRGAGEnxyACFGcdkjlsa52UwfYrwreEg2l +gW5AzqHgjFfj9QRLydeU/n4bHm0F1adMsV7P3rVwilcUlqsENDwXnWyPEyv3sw6F +wNemLEs1129mB77fwvySb+lLNGsnzr8w4wdioZ74co+T9z2ca+eUiP+EQccVw1Is +D4Fh57IjPa6Wuc4mwiUYKkKY63+38aCfEWb0Qoi+zW+mE9nek6MOQ914cN12u5LX +dBoYopphRO5YmubSN4xcBy405nIdSdbrAVWwxXnVVyjqjknmNeqQsPZaxAhdoKhV +AqxNr8AUAdOAO6Sz3MslmcLlDXFihrEEOeUbpg/m1mSUUHGbu966ajTG1FuEHHwS +7WB52yxoJo/tHvt9nAWnh3uH5BHmS8zn6s6CGweWKbX5yICnZ1QFR1e4pogxX39v +XD6YcNOO+Vn+HY4nXmjgSYVC7l+eeP8eduMg1xJujzjrbmrXU+d+cBObgdTOAlpa +JFHaGwYw1osAwPCo9cZ2f04yitBfj9aPFia8ASKldakCAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUqKS+ltlior0SyZKYAkJ/efv55towDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQAdElvp8bW4B+Cv+1WSN87dg6TN +wGyIjJ14/QYURgyrZiYpUmZpj+/pJmprSWXu4KNyqHftmaidu7cdjL5nCAvAfnY5 +/6eDDbX4j8Gt9fb/6H9y0O0dn3mUPSEKG0crR+JRFAtPhn/2FNvst2P82yguWLv0 +pHjHVUVcq+HqDMtUIJsTPYjSh9Iy77Q6TOZKln9dyDOWJpCSkiUWQtMAKbCSlvzd +zTs/ahqpT+zLfGR1SR+T3snZHgQnbnemmz/XtlKl52NxccARwfcEEKaCRQyGq/pR +0PVZasyJS9JY4JfQs4YOdeOt4UMZ8BmW1+BQWGSkkb0QIRl8CszoKofucAlqdPcO +IT/ZaMVhI580LFGWiQIizWFskX6lqbCyHqJB3LDl8gJISB5vNTHOHpvpMOMs5PYt +cRl5Mrksx5MKMqG7y5R734nMlZxQIHjL5FOoOxTBp9KeWIL/Ib89T2QDaLw1SQ+w +ihqWBJ4ZdrIMWYpP3WqM+MXWk7WAem+xsFJdR+MDgOOuobVQTy5dGBlPks/6gpjm +rO9TjfQ36ppJ3b7LdKUPeRfnYmlR5RU4oyYJ//uLbClI443RZAgxaCXX/nyc12lr +eVLUMNF2abLX4/VF63m2/Z9ACgMRfqGshPssn1NN33OonrotQoj4S3N9ZrjvzKt8 +iHcaqd60QKpfiH2A3A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj2gAwIBAgIQPaVGRuu86nh/ylZVCLB0MzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMDMxNloYDzIxMjEwNTI1MjMwMzE2WjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLW5vcnRoZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEexNURoB9KE93MEtEAlJG +obz4LS/pD2hc8Gczix1WhVvpJ8bN5zCDXaKdnDMCebetyRQsmQ2LYlfmCwpZwSDu +0zowB11Pt3I5Avu2EEcuKTlKIDMBeZ1WWuOd3Tf7MEAMo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSaYbZPBvFLikSAjpa8mRJvyArMxzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaQAwZgIxAOEJkuh3Zjb7Ih/zuNRd1RBqmIYcnyw0 +nwUZczKXry+9XebYj3VQxSRNadrarPWVqgIxAMg1dyGoDAYjY/L/9YElyMnvHltO +PwpJShmqHvCLc/mXMgjjYb/akK7yGthvW6j/uQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQChu3v5W1Doil3v6pgRIcVzANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIEJldGEgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MTgyMTM0MTVaGA8yMTIxMDUxODIyMzQxNVow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC1 +FUGQ5tf3OwpDR6hGBxhUcrkwKZhaXP+1St1lSOQvjG8wXT3RkKzRGMvb7Ee0kzqI +mzKKe4ASIhtV3UUWdlNmP0EA3XKnif6N79MismTeGkDj75Yzp5A6tSvqByCgxIjK +JqpJrch3Dszoyn8+XhwDxMZtkUa5nQVdJgPzJ6ltsQ8E4SWLyLtTu0S63jJDkqYY +S7cQblk7y7fel+Vn+LS5dGTdRRhMvSzEnb6mkVBaVzRyVX90FNUED06e8q+gU8Ob +htvQlf9/kRzHwRAdls2YBhH40ZeyhpUC7vdtPwlmIyvW5CZ/QiG0yglixnL6xahL +pbmTuTSA/Oqz4UGQZv2WzHe1lD2gRHhtFX2poQZeNQX8wO9IcUhrH5XurW/G9Xwl +Sat9CMPERQn4KC3HSkat4ir2xaEUrjfg6c4XsGyh2Pk/LZ0gLKum0dyWYpWP4JmM +RQNjrInXPbMhzQObozCyFT7jYegS/3cppdyy+K1K7434wzQGLU1gYXDKFnXwkX8R +bRKgx2pHNbH5lUddjnNt75+e8m83ygSq/ZNBUz2Ur6W2s0pl6aBjwaDES4VfWYlI +jokcmrGvJNDfQWygb1k00eF2bzNeNCHwgWsuo3HSxVgc/WGsbcGrTlDKfz+g3ich +bXUeUidPhRiv5UQIVCLIHpHuin3bj9lQO/0t6p+tAQIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBSFmMBgm5IsRv3hLrvDPIhcPweXYTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAAa2EuozymOsQDJlEi7TqnyA2OhT +GXPfYqCyMJVkfrqNgcnsNpCAiNEiZbb+8sIPXnT8Ay8hrwJYEObJ5b7MHXpLuyft +z0Pu1oFLKnQxKjNxrIsCvaB4CRRdYjm1q7EqGhMGv76se9stOxkOqO9it31w/LoU +ENDk7GLsSqsV1OzYLhaH8t+MaNP6rZTSNuPrHwbV3CtBFl2TAZ7iKgKOhdFz1Hh9 +Pez0lG+oKi4mHZ7ajov6PD0W7njn5KqzCAkJR6OYmlNVPjir+c/vUtEs0j+owsMl +g7KE5g4ZpTRShyh5BjCFRK2tv0tkqafzNtxrKC5XNpEkqqVTCnLcKG+OplIEadtr +C7UWf4HyhCiR+xIyxFyR05p3uY/QQU/5uza7GlK0J+U1sBUytx7BZ+Fo8KQfPPqV +CqDCaYUksoJcnJE/KeoksyqNQys7sDGJhkd0NeUGDrFLKHSLhIwAMbEWnqGxvhli +E7sP2E5rI/I9Y9zTbLIiI8pfeZlFF8DBdoP/Hzg8pqsiE/yiXSFTKByDwKzGwNqz +F0VoFdIZcIbLdDbzlQitgGpJtvEL7HseB0WH7B2PMMD8KPJlYvPveO3/6OLzCsav ++CAkvk47NQViKMsUTKOA0JDCW+u981YRozxa3K081snhSiSe83zIPBz1ikldXxO9 +6YYLNPRrj3mi9T/f +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAMkvdFnVDb0mWWFiXqnKH68wCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkxMzI0WhgPMjEyMTA1MTkyMDEzMjRaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEy86DB+9th/0A5VcWqMSWDxIUblWTt/R0 +ao6Z2l3vf2YDF2wt1A2NIOGpfQ5+WAOJO/IQmnV9LhYo+kacB8sOnXdQa6biZZkR +IyouUfikVQAKWEJnh1Cuo5YMM4E2sUt5o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBQ8u3OnecANmG8OoT7KLWDuFzZwBTAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIwQ817qkb7mWJFnieRAN+m9W3E0FLVKaV3zC5aYJUk2fcZ +TaUx3oLp3jPLGvY5+wgeAjEA6wAicAki4ZiDfxvAIuYiIe1OS/7H5RA++R8BH6qG +iRzUBM/FItFpnkus7u/eTkvo +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQS/+Ryfgb/IOVEa1pWoe8oTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFwLXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNjA2MjE1NDQyWhgPMjEyMjA2MDYyMjU0NDJaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYXAtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDsX6fhdUWBQpYTdseBD/P3s96Dtw2Iw +OrXKNToCnmX5nMkUGdRn9qKNiz1pw3EPzaPxShbYwQ7LYP09ENK/JN4QQjxMihxC +jLFxS85nhBQQQGRCWikDAe38mD8fSvREQKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUIh1xZiseQYFjPYKJmGbruAgRH+AwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMFudS4zLy+UUGrtgNLtRMcu/DZ9BUzV4NdHxo0bkG44O +thnjl4+wTKI6VbyAbj2rkgIxAOHps8NMITU5DpyiMnKTxV8ubb/WGHrLl0BjB8Lw +ETVJk5DNuZvsIIcm7ykk6iL4Tw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQDcEmNIAVrDpUw5cH5ynutDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIG1lLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNTA3MDA0MDIzWhgPMjEyMjA1MDcwMTQwMjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKvADk8t +Fl9bFlU5sajLPPDSOUpPAkKs6iPlz+27o1GJC88THcOvf3x0nVAcu9WYe9Qaas+4 +j4a0vv51agqyODRD/SNi2HnqW7DbtLPAm6KBHe4twl28ItB/JD5g7u1oPAHFoXMS +cH1CZEAs5RtlZGzJhcBXLFsHNv/7+SCLyZ7+2XFh9OrtgU4wMzkHoRNndhfwV5bu +17bPTwuH+VxH37zXf1mQ/KjhuJos0C9dL0FpjYBAuyZTAWhZKs8dpSe4DI544z4w +gkwUB4bC2nA1TBzsywEAHyNuZ/xRjNpWvx0ToWAA2iFJqC3VO3iKcnBplMvaUuMt +jwzVSNBnKcoabXCZL2XDLt4YTZR8FSwz05IvsmwcPB7uNTBXq3T9sjejW8QQK3vT +tzyfLq4jKmQE7PoS6cqYm+hEPm2hDaC/WP9bp3FdEJxZlPH26fq1b7BWYWhQ9pBA +Nv9zTnzdR1xohTyOJBUFQ81ybEzabqXqVXUIANqIOaNcTB09/sLJ7+zuMhp3mwBu +LtjfJv8PLuT1r63bU3seROhKA98b5KfzjvbvPSg3vws78JQyoYGbqNyDfyjVjg3U +v//AdVuPie6PNtdrW3upZY4Qti5IjP9e3kimaJ+KAtTgMRG56W0WxD3SP7+YGGbG +KhntDOkKsN39hLpn9UOafTIqFu7kIaueEy/NAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFHAems86dTwdZbLe8AaPy3kfIUVoMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOBHpp0ICx81kmeoBcZTrMdJs2gnhcd85 +FoSCjXx9H5XE5rmN/lQcxxOgj8hr3uPuLdLHu+i6THAyzjrl2NA1FWiqpfeECGmy +0jm7iZsYORgGQYp/VKnDrwnKNSqlZvOuRr0kfUexwFlr34Y4VmupvEOK/RdGsd3S ++3hiemcHse9ST/sJLHx962AWMkN86UHPscJEe4+eT3f2Wyzg6La8ARwdWZSNS+WH +ZfybrncMmuiXuUdHv9XspPsqhKgtHhcYeXOGUtrwQPLe3+VJZ0LVxhlTWr9951GZ +GfmWwTV/9VsyKVaCFIXeQ6L+gjcKyEzYF8wpMtQlSc7FFqwgC4bKxvMBSaRy88Nr +lV2+tJD/fr8zGUeBK44Emon0HKDBWGX+/Hq1ZIv0Da0S+j6LbA4fusWxtGfuGha+ +luhHgVInCpALIOamiBEdGhILkoTtx7JrYppt3/Raqg9gUNCOOYlCvGhqX7DXeEfL +DGabooiY2FNWot6h04JE9nqGj5QqT8D6t/TL1nzxhRPzbcSDIHUd/b5R+a0bAA+7 +YTU6JqzEVCWKEIEynYmqikgLMGB/OzWsgyEL6822QW6hJAQ78XpbNeCzrICF4+GC +7KShLnwuWoWpAb26268lvOEvCTFM47VC6jNQl97md+2SA9Ma81C9wflid2M83Wle +cuLMVcQZceE= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQAhAteLRCvizAElaWORFU2zANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE3MDkxNloYDzIwNjEwNTIwMTgwOTE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+qg7JAcOVKjh +N83SACnBFZPyB63EusfDr/0V9ZdL8lKcmZX9sv/CqoBo3N0EvBqHQqUUX6JvFb7F +XrMUZ740kr28gSRALfXTFgNODjXeDsCtEkKRTkac/UM8xXHn+hR7UFRPHS3e0GzI +iLiwQWDkr0Op74W8aM0CfaVKvh2bp4BI1jJbdDnQ9OKXpOxNHGUf0ZGb7TkNPkgI +b2CBAc8J5o3H9lfw4uiyvl6Fz5JoP+A+zPELAioYBXDrbE7wJeqQDJrETWqR9VEK +BXURCkVnHeaJy123MpAX2ozf4pqk0V0LOEOZRS29I+USF5DcWr7QIXR/w2I8ws1Q +7ys+qbE+kQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQFJ16n +1EcCMOIhoZs/F9sR+Jy++zAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAOc5nXbT3XTDEZsxX2iD15YrQvmL5m13B3ImZWpx/pqmObsgx3/dg75rF2nQ +qS+Vl+f/HLh516pj2BPP/yWCq12TRYigGav8UH0qdT3CAClYy2o+zAzUJHm84oiB +ud+6pFVGkbqpsY+QMpJUbZWu52KViBpJMYsUEy+9cnPSFRVuRAHjYynSiLk2ZEjb +Wkdc4x0nOZR5tP0FgrX0Ve2KcjFwVQJVZLgOUqmFYQ/G0TIIGTNh9tcmR7yp+xJR +A2tbPV2Z6m9Yxx4E8lLEPNuoeouJ/GR4CkMEmF8cLwM310t174o3lKKUXJ4Vs2HO +Wj2uN6R9oI+jGLMSswTzCNV1vgc= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICuDCCAj6gAwIBAgIRAOocLeZWjYkG/EbHmscuy8gwCgYIKoZIzj0EAwMwgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjEyMTUwMDFaGA8yMTIxMDUyMTIyNTAwMVowgZsx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE0MDIGA1UEAwwrQW1h +em9uIFJEUyBhcC1zb3V0aGVhc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABCEr3jq1KtRncnZfK5cq +btY0nW6ZG3FMbh7XwBIR6Ca0f8llGZ4vJEC1pXgiM/4Dh045B9ZIzNrR54rYOIfa +2NcYZ7mk06DjIQML64hbAxbQzOAuNzLPx268MrlL2uW2XaNCMEAwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQUln75pChychwN4RfHl+tOinMrfVowDgYDVR0PAQH/ +BAQDAgGGMAoGCCqGSM49BAMDA2gAMGUCMGiyPINRU1mwZ4Crw01vpuPvxZxb2IOr +yX3RNlOIu4We1H+5dQk5tIvH8KGYFbWEpAIxAO9NZ6/j9osMhLgZ0yj0WVjb+uZx +YlZR9fyFisY/jNfX7QhSk+nrc3SFLRUNtpXrng== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAKiaRZatN8eiz9p0s0lu0rQwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDIzNVoYDzIwNjEwNTIxMjMwMjM1WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCygVMf +qB865IR9qYRBRFHn4eAqGJOCFx+UbraQZmjr/mnRqSkY+nhbM7Pn/DWOrRnxoh+w +q5F9ZxdZ5D5T1v6kljVwxyfFgHItyyyIL0YS7e2h7cRRscCM+75kMedAP7icb4YN +LfWBqfKHbHIOqvvQK8T6+Emu/QlG2B5LvuErrop9K0KinhITekpVIO4HCN61cuOe +CADBKF/5uUJHwS9pWw3uUbpGUwsLBuhJzCY/OpJlDqC8Y9aToi2Ivl5u3/Q/sKjr +6AZb9lx4q3J2z7tJDrm5MHYwV74elGSXoeoG8nODUqjgklIWAPrt6lQ3WJpO2kug +8RhCdSbWkcXHfX95AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FOIxhqTPkKVqKBZvMWtKewKWDvDBMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEAqoItII89lOl4TKvg0I1EinxafZLXIheLcdGCxpjRxlZ9QMQUN3yb +y/8uFKBL0otbQgJEoGhxm4h0tp54g28M6TN1U0332dwkjYxUNwvzrMaV5Na55I2Z +1hq4GB3NMXW+PvdtsgVOZbEN+zOyOZ5MvJHEQVkT3YRnf6avsdntltcRzHJ16pJc +Y8rR7yWwPXh1lPaPkxddrCtwayyGxNbNmRybjR48uHRhwu7v2WuAMdChL8H8bp89 +TQLMrMHgSbZfee9hKhO4Zebelf1/cslRSrhkG0ESq6G5MUINj6lMg2g6F0F7Xz2v +ncD/vuRN5P+vT8th/oZ0Q2Gc68Pun0cn/g== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAJYlnmkGRj4ju/2jBQsnXJYwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMDQ0NFoYDzIwNjEwNTIyMDAwNDQ0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC74V3eigv+pCj5 +nqDBqplY0Jp16pTeNB06IKbzb4MOTvNde6QjsZxrE1xUmprT8LxQqN9tI3aDYEYk +b9v4F99WtQVgCv3Y34tYKX9NwWQgwS1vQwnIR8zOFBYqsAsHEkeJuSqAB12AYUSd +Zv2RVFjiFmYJho2X30IrSLQfS/IE3KV7fCyMMm154+/K1Z2IJlcissydEAwgsUHw +edrE6CxJVkkJ3EvIgG4ugK/suxd8eEMztaQYJwSdN8TdfT59LFuSPl7zmF3fIBdJ +//WexcQmGabaJ7Xnx+6o2HTfkP8Zzzzaq8fvjAcvA7gyFH5EP26G2ZqMG+0y4pTx +SPVTrQEXAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFIWWuNEF +sUMOC82XlfJeqazzrkPDMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAgClmxcJaQTGpEZmjElL8G2Zc8lGc+ylGjiNlSIw8X25/bcLRptbDA90nuP+q +zXAMhEf0ccbdpwxG/P5a8JipmHgqQLHfpkvaXx+0CuP++3k+chAJ3Gk5XtY587jX ++MJfrPgjFt7vmMaKmynndf+NaIJAYczjhJj6xjPWmGrjM3MlTa9XesmelMwP3jep +bApIWAvCYVjGndbK9byyMq1nyj0TUzB8oJZQooaR3MMjHTmADuVBylWzkRMxbKPl +4Nlsk4Ef1JvIWBCzsMt+X17nuKfEatRfp3c9tbpGlAE/DSP0W2/Lnayxr4RpE9ds +ICF35uSis/7ZlsftODUe8wtpkQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQS7vMpOTVq2Jw457NdZ2ffjAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGNhLXdlc3QtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMzA5MTkyMjExNDNaGA8yMTIzMDkxOTIzMTE0M1owgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBjYS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARdgGSs/F2lpWKqS1ZpcmatFED1JurmNbXG +Sqhv1A/geHrKCS15MPwjtnfZiujYKY4fNkCCUseoGDwkC4281nwkokvnfWR1/cXy +LxfACoXNxsI4b+37CezSUBl48/5p1/OjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFFhLokGBuJGwKJhZcYSYKyZIitJtMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA8aQQlzJRHbqFsRY4O3u/cN0T8dzjcqnYn4NV1w+jvhzt +QPJLB+ggGyQhoFR6G2UrAjEA0be8OP5MWXD8d01KKbo5Dpy6TwukF5qoJmkFJKS3 +bKfEMvFWxXoV06HNZFWdI80u +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAPvvd+MCcp8E36lHziv0xhMwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIzMTEwNloYDzIxMjEwNTIyMDAxMTA2WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDbvwekKIKGcV/s +lDU96a71ZdN2pTYkev1X2e2/ICb765fw/i1jP9MwCzs8/xHBEQBJSxdfO4hPeNx3 +ENi0zbM+TrMKliS1kFVe1trTTEaHYjF8BMK9yTY0VgSpWiGxGwg4tshezIA5lpu8 +sF6XMRxosCEVCxD/44CFqGZTzZaREIvvFPDTXKJ6yOYnuEkhH3OcoOajHN2GEMMQ +ShuyRFDQvYkqOC/Q5icqFbKg7eGwfl4PmimdV7gOVsxSlw2s/0EeeIILXtHx22z3 +8QBhX25Lrq2rMuaGcD3IOMBeBo2d//YuEtd9J+LGXL9AeOXHAwpvInywJKAtXTMq +Wsy3LjhuANFrzMlzjR2YdjkGVzeQVx3dKUzJ2//Qf7IXPSPaEGmcgbxuatxjnvfT +H85oeKr3udKnXm0Kh7CLXeqJB5ITsvxI+Qq2iXtYCc+goHNR01QJwtGDSzuIMj3K +f+YMrqBXZgYBwU2J/kCNTH31nfw96WTbOfNGwLwmVRDgguzFa+QzmQsJW4FTDMwc +7cIjwdElQQVA+Gqa67uWmyDKAnoTkudmgAP+OTBkhnmc6NJuZDcy6f/iWUdl0X0u +/tsfgXXR6ZovnHonM13ANiN7VmEVqFlEMa0VVmc09m+2FYjjlk8F9sC7Rc4wt214 +7u5YvCiCsFZwx44baP5viyRZgkJVpQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBQgCZCsc34nVTRbWsniXBPjnUTQ2DAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAAQas3x1G6OpsIvQeMS9BbiHG3+kU9P/ba6Rrg+E +lUz8TmL04Bcd+I+R0IyMBww4NznT+K60cFdk+1iSmT8Q55bpqRekyhcdWda1Qu0r +JiTi7zz+3w2v66akofOnGevDpo/ilXGvCUJiLOBnHIF0izUqzvfczaMZGJT6xzKq +PcEVRyAN1IHHf5KnGzUlVFv9SGy47xJ9I1vTk24JU0LWkSLzMMoxiUudVmHSqJtN +u0h+n/x3Q6XguZi1/C1KOntH56ewRh8n5AF7c+9LJJSRM9wunb0Dzl7BEy21Xe9q +03xRYjf5wn8eDELB8FZPa1PrNKXIOLYM9egdctbKEcpSsse060+tkyBrl507+SJT +04lvJ4tcKjZFqxn+bUkDQvXYj0D3WK+iJ7a8kZJPRvz8BDHfIqancY8Tgw+69SUn +WqIb+HNZqFuRs16WFSzlMksqzXv6wcDSyI7aZOmCGGEcYW9NHk8EuOnOQ+1UMT9C +Qb1GJcipjRzry3M4KN/t5vN3hIetB+/PhmgTO4gKhBETTEyPC3HC1QbdVfRndB6e +U/NF2U/t8U2GvD26TTFLK4pScW7gyw4FQyXWs8g8FS8f+R2yWajhtS9++VDJQKom +fAUISoCH+PlPRJpu/nHd1Zrddeiiis53rBaLbXu2J1Q3VqjWOmtj0HjxJJxWnYmz +Pqj2 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAI/U4z6+GF8/znpHM8Dq8G0wDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA2MDYyMTQ4MThaGA8yMTIyMDYwNjIyNDgxOFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK5WqMvyq888 +3uuOtEj1FcP6iZhqO5kJurdJF59Otp2WCg+zv6I+QwaAspEWHQsKD405XfFsTGKV +SKTCwoMxwBniuChSmyhlagQGKSnRY9+znOWq0v7hgmJRwp6FqclTbubmr+K6lzPy +hs86mEp68O5TcOTYWUlPZDqfKwfNTbtCl5YDRr8Gxb5buHmkp6gUSgDkRsXiZ5VV +b3GBmXRqbnwo5ZRNAzQeM6ylXCn4jKs310lQGUrFbrJqlyxUdfxzqdlaIRn2X+HY +xRSYbHox3LVNPpJxYSBRvpQVFSy9xbX8d1v6OM8+xluB31cbLBtm08KqPFuqx+cO +I2H5F0CYqYzhyOSKJsiOEJT6/uH4ewryskZzncx9ae62SC+bB5n3aJLmOSTkKLFY +YS5IsmDT2m3iMgzsJNUKVoCx2zihAzgBanFFBsG+Xmoq0aKseZUI6vd2qpd5tUST +/wS1sNk0Ph7teWB2ACgbFE6etnJ6stwjHFZOj/iTYhlnR2zDRU8akunFdGb6CB4/ +hMxGJxaqXSJeGtHm7FpadlUTf+2ESbYcVW+ui/F8sdBJseQdKZf3VdZZMgM0bcaX +NE47cauDTy72WdU9YJX/YXKYMLDE0iFHTnGpfVGsuWGPYhlwZ3dFIO07mWnCRM6X +u5JXRB1oy5n5HRluMsmpSN/R92MeBxKFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFNtH0F0xfijSLHEyIkRGD9gW6NazMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEACo+5jFeY3ygxoDDzL3xpfe5M0U1WxdKk+az4 +/OfjZvkoma7WfChi3IIMtwtKLYC2/seKWA4KjlB3rlTsCVNPnK6D+gAnybcfTKk/ +IRSPk92zagwQkSUWtAk80HpVfWJzpkSU16ejiajhedzOBRtg6BwsbSqLCDXb8hXr +eXWC1S9ZceGc+LcKRHewGWPu31JDhHE9bNcl9BFSAS0lYVZqxIRWxivZ+45j5uQv +wPrC8ggqsdU3K8quV6dblUQzzA8gKbXJpCzXZihkPrYpQHTH0szvXvgebh+CNUAG +rUxm8+yTS0NFI3U+RLbcLFVzSvjMOnEwCX0SPj5XZRYYXs5ajtQCoZhTUkkwpDV8 +RxXk8qGKiXwUxDO8GRvmvM82IOiXz5w2jy/h7b7soyIgdYiUydMq4Ja4ogB/xPZa +gf4y0o+bremO15HFf1MkaU2UxPK5FFVUds05pKvpSIaQWbF5lw4LHHj4ZtVup7zF +CLjPWs4Hs/oUkxLMqQDw0FBwlqa4uot8ItT8uq5BFpz196ZZ+4WXw5PVzfSxZibI +C/nwcj0AS6qharXOs8yPnPFLPSZ7BbmWzFDgo3tpglRqo3LbSPsiZR+sLeivqydr +0w4RK1btRda5Ws88uZMmW7+2aufposMKcbAdrApDEAVzHijbB/nolS5nsnFPHZoA +KDPtFEk= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQVZ5Y/KqjR4XLou8MCD5pOjAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyNTE2NTgzM1oYDzIxMjIwNTI1MTc1ODMzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC00IFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEbo473OmpD5vkckdJajXg +brhmNFyoSa0WCY1njuZC2zMFp3zP6rX4I1r3imrYnJd9pFH/aSiV/r6L5ACE5RPx +4qdg5SQ7JJUaZc3DWsTOiOed7BCZSzM+KTYK/2QzDMApo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBTmogc06+1knsej1ltKUOdWFvwgsjAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAIs7TlLMbGTWNXpGiKf9DxaM07d/iDHe +F/Vv/wyWSTGdobxBL6iArQNVXz0Gr4dvPAIwd0rsoa6R0x5mtvhdRPtM37FYrbHJ +pbV+OMusQqcSLseunLBoCHenvJW0QOCQ8EDY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAO9dVdiLTEGO8kjUFExJmgowDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIyMTIwMjIwMjYwOFoYDzIxMjIxMjAyMjEyNjA4WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDkVHmJ +bUc8CNDGBcgPmXHSHj5dS1PDnnpk3doCu6pahyYXW8tqAOmOqsDuNz48exY7YVy4 +u9I9OPBeTYB9ZUKwxq+1ZNLsr1cwVz5DdOyDREVFOjlU4rvw0eTgzhP5yw/d+Ai/ ++WmPebZG0irwPKN2f60W/KJ45UNtR+30MT8ugfnPuSHWjjV+dqCOCp/mj8nOCckn +k8GoREwjuTFJMKInpQUC0BaVVX6LiIdgtoLY4wdx00EqNBuROoRTAvrked0jvm7J +UI39CSYxhNZJ9F6LdESZXjI4u2apfNQeSoy6WptxFHr+kh2yss1B2KT6lbwGjwWm +l9HODk9kbBNSy2NeewAms36q+p8wSLPavL28IRfK0UaBAiN1hr2a/2RDGCwOJmw6 +5erRC5IIX5kCStyXPEGhVPp18EvMuBd37eLIxjZBBO8AIDf4Ue8QmxSeZH0cT204 +3/Bd6XR6+Up9iMTxkHr1URcL1AR8Zd62lg/lbEfxePNMK9mQGxKP8eTMG5AjtW9G +TatEoRclgE0wZQalXHmKpBNshyYdGqQZhzL1MxCxWzfHNgZkTKIsdzxrjnP7RiBR +jdRH0YhXn6Y906QfLwMCaufwfQ5J8+nj/tu7nG138kSxsu6VUkhnQJhUcUsxuHD/ +NnBx0KGVEldtZiZf7ccgtRVp1lA0OrVtq3ZLMQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ2WC3p8rWeE2N0S4Om01KsNLpk/jAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAFFEVDt45Obr6Ax9E4RMgsKjj4QjMFB9 +wHev1jL7hezl/ULrHuWxjIusaIZEIcKfn+v2aWtqOq13P3ht7jV5KsV29CmFuCdQ +q3PWiAXVs+hnMskTOmGMDnptqd6/UuSIha8mlOKKAvnmRQJvfX9hIfb/b/mVyKWD +uvTTmcy3cOTJY5ZIWGyzuvmcqA0YNcb7rkJt/iaLq4RX3/ofq4y4w36hefbcvj++ +pXHOmXk3dAej3y6SMBOUcGMyCJcCluRPNYKDTLn+fitcPxPC3JG7fI5bxQ0D6Hpa +qbyGBQu96sfahQyMc+//H8EYlo4b0vPeS5RFFXJS/VBf0AyNT4vVc7H17Q6KjeNp +wEARqsIa7UalHx9MnxrQ/LSTTxiC8qmDkIFuQtw8iQMN0SoL5S0eCZNRD31awgaY +y1PvY8JMN549ugIUjOXnown/OxharLW1evWUraU5rArq3JfeFpPXl4K/u10T5SCL +iJRoxFilGPMFE3hvnmbi5rEy8wRUn7TpLb4I4s/CB/lT2qZTPqvQHwxKCnMm9BKF +NHb4rLL5dCvUi5NJ6fQ/exOoGdOVSfT7jqFeq2TtNunERSz9vpriweliB6iIe1Al +Thj8aEs1GqA764rLVGA+vUe18NhjJm9EemrdIzjSQFy/NdbN/DMaHqEzJogWloAI +izQWYnCS19TJ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICvTCCAkOgAwIBAgIQCIY7E/bFvFN2lK9Kckb0dTAKBggqhkjOPQQDAzCBnjEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5BbWF6 +b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUxODIxMDUxMFoYDzIxMjEwNTE4MjIwNTEwWjCB +njELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTcwNQYDVQQDDC5B +bWF6b24gUkRTIFByZXZpZXcgdXMtZWFzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEMI0hzf1JCEOI +Eue4+DmcNnSs2i2UaJxHMrNGGfU7b42a7vwP53F7045ffHPBGP4jb9q02/bStZzd +VHqfcgqkSRI7beBKjD2mfz82hF/wJSITTgCLs+NRpS6zKMFOFHUNo0IwQDAPBgNV +HRMBAf8EBTADAQH/MB0GA1UdDgQWBBS8uF/6hk5mPLH4qaWv9NVZaMmyTjAOBgNV +HQ8BAf8EBAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIxAO7Pu9wzLyM0X7Q08uLIL+vL +qaxe3UFuzFTWjM16MLJHbzLf1i9IDFKz+Q4hXCSiJwIwClMBsqT49BPUxVsJnjGr +EbyEk6aOOVfY1p2yQL649zh3M4h8okLnwf+bYIb1YpeU +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQY+JhwFEQTe36qyRlUlF8ozANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE5MjQxNloYDzIwNjEwNTE5MjAyNDE2WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnIye77j6ev40 +8wRPyN2OdKFSUfI9jB20Or2RLO+RDoL43+USXdrze0Wv4HMRLqaen9BcmCfaKMp0 +E4SFo47bXK/O17r6G8eyq1sqnHE+v288mWtYH9lAlSamNFRF6YwA7zncmE/iKL8J +0vePHMHP/B6svw8LULZCk+nZk3tgxQn2+r0B4FOz+RmpkoVddfqqUPMbKUxhM2wf +fO7F6bJaUXDNMBPhCn/3ayKCjYr49ErmnpYV2ZVs1i34S+LFq39J7kyv6zAgbHv9 ++/MtRMoRB1CjpqW0jIOZkHBdYcd1o9p1zFn591Do1wPkmMsWdjIYj+6e7UXcHvOB +2+ScIRAcnwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBQGtq2W +YSyMMxpdQ3IZvcGE+nyZqTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEgoP3ixJsKSD5FN8dQ01RNHERl/IFbA7TRXfwC+L1yFocKnQh4Mp/msPRSV ++OeHIvemPW/wtZDJzLTOFJ6eTolGekHK1GRTQ6ZqsWiU2fmiOP8ks4oSpI+tQ9Lw +VrfZqTiEcS5wEIqyfUAZZfKDo7W1xp+dQWzfczSBuZJZwI5iaha7+ILM0r8Ckden +TVTapc5pLSoO15v0ziRuQ2bT3V3nwu/U0MRK44z+VWOJdSiKxdnOYDs8hFNnKhfe +klbTZF7kW7WbiNYB43OaAQBJ6BALZsIskEaqfeZT8FD71uN928TcEQyBDXdZpRN+ +iGQZDGhht0r0URGMDSs9waJtTfA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQXY/dmS+72lZPranO2JM9jjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGFwLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjEzNDUxWhgPMjEyMTA1MjUyMjM0NTFaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgYXAtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMyW9kBJjD/hx8e8 +b5E1sF42bp8TXsz1htSYE3Tl3T1Aq379DfEhB+xa/ASDZxt7/vwa81BkNo4M6HYq +okYIXeE7cu5SnSgjWXqcERhgPevtAwgmhdE3yREe8oz2DyOi2qKKZqah+1gpPaIQ +fK0uAqoeQlyHosye3KZZKkDHBatjBsQ5kf8lhuf7wVulEZVRHY2bP2X7N98PfbpL +QdH7mWXzDtJJ0LiwFwds47BrkgK1pkHx2p1mTo+HMkfX0P6Fq1atkVC2RHHtbB/X +iYyH7paaHBzviFrhr679zNqwXIOKlbf74w3mS11P76rFn9rS1BAH2Qm6eY5S/Fxe +HEKXm4kjPN63Zy0p3yE5EjPt54yPkvumOnT+RqDGJ2HCI9k8Ehcbve0ogfdRKNqQ +VHWYTy8V33ndQRHZlx/CuU1yN61TH4WSoMly1+q1ihTX9sApmlQ14B2pJi/9DnKW +cwECrPy1jAowC2UJ45RtC8UC05CbP9yrIy/7Noj8gQDiDOepm+6w1g6aNlWoiuQS +kyI6nzz1983GcnOHya73ga7otXo0Qfg9jPghlYiMomrgshlSLDHZG0Ib/3hb8cnR +1OcN9FpzNmVK2Ll1SmTMLrIhuCkyNYX9O/bOknbcf706XeESxGduSkHEjIw/k1+2 +Atteoq5dT6cwjnJ9hyhiueVlVkiDAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFLUI+DD7RJs+0nRnjcwIVWzzYSsFMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAb1mcCHv4qMQetLGTBH9IxsB2YUUhr5dda0D2BcHr +UtDbfd0VQs4tux6h/6iKwHPx0Ew8fuuYj99WknG0ffgJfNc5/fMspxR/pc1jpdyU +5zMQ+B9wi0lOZPO9uH7/pr+d2odcNEy8zAwqdv/ihsTwLmGP54is9fVbsgzNW1cm +HKAVL2t/Ope+3QnRiRilKCN1lzhav4HHdLlN401TcWRWKbEuxF/FgxSO2Hmx86pj +e726lweCTMmnq/cTsPOVY0WMjs0or3eHDVlyLgVeV5ldyN+ptg3Oit60T05SRa58 +AJPTaVKIcGQ/gKkKZConpu7GDofT67P/ox0YNY57LRbhsx9r5UY4ROgz7WMQ1yoS +Y+19xizm+mBm2PyjMUbfwZUyCxsdKMwVdOq5/UmTmdms+TR8+m1uBHPOTQ2vKR0s +Pd/THSzPuu+d3dbzRyDSLQbHFFneG760CUlD/ZmzFlQjJ89/HmAmz8IyENq+Sjhx +Jgzy+FjVZb8aRUoYLlnffpUpej1n87Ynlr1GrvC4GsRpNpOHlwuf6WD4W0qUTsC/ +C9JO+fBzUj/aWlJzNcLEW6pte1SB+EdkR2sZvWH+F88TxemeDrV0jKJw5R89CDf8 +ZQNfkxJYjhns+YeV0moYjqQdc7tq4i04uggEQEtVzEhRLU5PE83nlh/K2NZZm8Kj +dIA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAPVSMfFitmM5PhmbaOFoGfUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMzQ1N1oYDzIwNjEwNTI1MjMzNDU3WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDu9H7TBeGoDzMr +dxN6H8COntJX4IR6dbyhnj5qMD4xl/IWvp50lt0VpmMd+z2PNZzx8RazeGC5IniV +5nrLg0AKWRQ2A/lGGXbUrGXCSe09brMQCxWBSIYe1WZZ1iU1IJ/6Bp4D2YEHpXrW +bPkOq5x3YPcsoitgm1Xh8ygz6vb7PsvJvPbvRMnkDg5IqEThapPjmKb8ZJWyEFEE +QRrkCIRueB1EqQtJw0fvP4PKDlCJAKBEs/y049FoOqYpT3pRy0WKqPhWve+hScMd +6obq8kxTFy1IHACjHc51nrGII5Bt76/MpTWhnJIJrCnq1/Uc3Qs8IVeb+sLaFC8K +DI69Sw6bAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE7PCopt +lyOgtXX0Y1lObBUxuKaCMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAFj+bX8gLmMNefr5jRJfHjrL3iuZCjf7YEZgn89pS4z8408mjj9z6Q5D1H7yS +jNETVV8QaJip1qyhh5gRzRaArgGAYvi2/r0zPsy+Tgf7v1KGL5Lh8NT8iCEGGXwF +g3Ir+Nl3e+9XUp0eyyzBIjHtjLBm6yy8rGk9p6OtFDQnKF5OxwbAgip42CD75r/q +p421maEDDvvRFR4D+99JZxgAYDBGqRRceUoe16qDzbMvlz0A9paCZFclxeftAxv6 +QlR5rItMz/XdzpBJUpYhdzM0gCzAzdQuVO5tjJxmXhkSMcDP+8Q+Uv6FA9k2VpUV +E/O5jgpqUJJ2Hc/5rs9VkAPXeA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQW0yuFCle3uj4vWiGU0SaGzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGFmLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTE5MTkzNTE2WhgPMjEyMTA1MTkyMDM1MTZaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgYWYtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABDPiKNZSaXs3Un/J/v+LTsFDANHpi7en +oL2qh0u0DoqNzEBTbBjvO23bLN3k599zh6CY3HKW0r2k1yaIdbWqt4upMCRCcUFi +I4iedAmubgzh56wJdoMZztjXZRwDthTkJKNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUWbYkcrvVSnAWPR5PJhIzppcAnZIwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMCESGqpat93CjrSEjE7z+Hbvz0psZTHwqaxuiH64GKUm +mYynIiwpKHyBrzjKBmeDoQIxANGrjIo6/b8Jl6sdIZQI18V0pAyLfLiZjlHVOnhM +MOTVgr82ZuPoEHTX78MxeMnYlw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAIbsx8XOl0sgTNiCN4O+18QwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTI1MjE1NDU4WhgPMjA2MTA1MjUyMjU0NTha +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +tROxwXWCgn5R9gI/2Ivjzaxc0g95ysBjoJsnhPdJEHQb7w3y2kWrVWU3Y9fOitgb +CEsnEC3PrhRnzNVW0fPsK6kbvOeCmjvY30rdbxbc8h+bjXfGmIOgAkmoULEr6Hc7 +G1Q/+tvv4lEwIs7bEaf+abSZxRJbZ0MBxhbHn7UHHDiMZYvzK+SV1MGCxx7JVhrm +xWu3GC1zZCsGDhB9YqY9eR6PmjbqA5wy8vqbC57dZZa1QVtWIQn3JaRXn+faIzHx +nLMN5CEWihsdmHBXhnRboXprE/OS4MFv1UrQF/XM/h5RBeCywpHePpC+Oe1T3LNC +iP8KzRFrjC1MX/WXJnmOVQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBS33XbXAUMs1znyZo4B0+B3D68WFTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBADuadd2EmlpueY2VlrIIPC30QkoA1EOSoCmZgN6124apkoY1 +HiV4r+QNPljN4WP8gmcARnNkS7ZeR4fvWi8xPh5AxQCpiaBMw4gcbTMCuKDV68Pw +P2dZCTMspvR3CDfM35oXCufdtFnxyU6PAyINUqF/wyTHguO3owRFPz64+sk3r2pT +WHmJjG9E7V+KOh0s6REgD17Gqn6C5ijLchSrPUHB0wOIkeLJZndHxN/76h7+zhMt +fFeNxPWHY2MfpcaLjz4UREzZPSB2U9k+y3pW1omCIcl6MQU9itGx/LpQE+H3ZeX2 +M2bdYd5L+ow+bdbGtsVKOuN+R9Dm17YpswF+vyQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAKlQ+3JX9yHXyjP/Ja6kZhkwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MTkxNzQ1MjBaGA8yMTIxMDUxOTE4NDUyMFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBhcC1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAKtahBrpUjQ6 +H2mni05BAKU6Z5USPZeSKmBBJN3YgD17rJ93ikJxSgzJ+CupGy5rvYQ0xznJyiV0 +91QeQN4P+G2MjGQR0RGeUuZcfcZitJro7iAg3UBvw8WIGkcDUg+MGVpRv/B7ry88 +7E4OxKb8CPNoa+a9j6ABjOaaxaI22Bb7j3OJ+JyMICs6CU2bgkJaj3VUV9FCNUOc +h9PxD4jzT9yyGYm/sK9BAT1WOTPG8XQUkpcFqy/IerZDfiQkf1koiSd4s5VhBkUn +aQHOdri/stldT7a+HJFVyz2AXDGPDj+UBMOuLq0K6GAT6ThpkXCb2RIf4mdTy7ox +N5BaJ+ih+Ro3ZwPkok60egnt/RN98jgbm+WstgjJWuLqSNInnMUgkuqjyBWwePqX +Kib+wdpyx/LOzhKPEFpeMIvHQ3A0sjlulIjnh+j+itezD+dp0UNxMERlW4Bn/IlS +sYQVNfYutWkRPRLErXOZXtlxxkI98JWQtLjvGzQr+jywxTiw644FSLWdhKa6DtfU +2JWBHqQPJicMElfZpmfaHZjtXuCZNdZQXWg7onZYohe281ZrdFPOqC4rUq7gYamL +T+ZB+2P+YCPOLJ60bj/XSvcB7mesAdg8P0DNddPhHUFWx2dFqOs1HxIVB4FZVA9U +Ppbv4a484yxjTgG7zFZNqXHKTqze6rBBAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFCEAqjighncv/UnWzBjqu1Ka2Yb4MA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAYyvumblckIXlohzi3QiShkZhqFzZultbFIu9 +GhA5CDar1IFMhJ9vJpO9nUK/camKs1VQRs8ZsBbXa0GFUM2p8y2cgUfLwFULAiC/ +sWETyW5lcX/xc4Pyf6dONhqFJt/ovVBxNZtcmMEWv/1D6Tf0nLeEb0P2i/pnSRR4 +Oq99LVFjossXtyvtaq06OSiUUZ1zLPvV6AQINg8dWeBOWRcQYhYcEcC2wQ06KShZ +0ahuu7ar5Gym3vuLK6nH+eQrkUievVomN/LpASrYhK32joQ5ypIJej3sICIgJUEP +UoeswJ+Z16f3ECoL1OSnq4A0riiLj1ZGmVHNhM6m/gotKaHNMxsK9zsbqmuU6IT/ +P6cR0S+vdigQG8ZNFf5vEyVNXhl8KcaJn6lMD/gMB2rY0qpaeTg4gPfU5wcg8S4Y +C9V//tw3hv0f2n+8kGNmqZrylOQDQWSSo8j8M2SRSXiwOHDoTASd1fyBEIqBAwzn +LvXVg8wQd1WlmM3b0Vrsbzltyh6y4SuKSkmgufYYvC07NknQO5vqvZcNoYbLNea3 +76NkFaMHUekSbwVejZgG5HGwbaYBgNdJEdpbWlA3X4yGRVxknQSUyt4dZRnw/HrX +k8x6/wvtw7wht0/DOqz1li7baSsMazqxx+jDdSr1h9xML416Q4loFCLgqQhil8Jq +Em4Hy3A= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQFn6AJ+uxaPDpNVx7174CpjANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIxMjAyMjAxNDA4WhgPMjA2MjEyMDIyMTE0MDhaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgaWwtY2VudHJhbC0xIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL2xGTSJ +fXorki/dkkTqdLyv4U1neeFYEyUCPN/HJ7ZloNwhj8RBrHYhZ4qtvUAvN+rs8fUm +L0wmaL69ye61S+CSfDzNwBDGwOzUm/cc1NEJOHCm8XA0unBNBvpJTjsFk2LQ+rz8 +oU0lVV4mjnfGektrTDeADonO1adJvUTYmF6v1wMnykSkp8AnW9EG/6nwcAJuAJ7d +BfaLThm6lfxPdsBNG81DLKi2me2TLQ4yl+vgRKJi2fJWwA77NaDqQuD5upRIcQwt +5noJt2kFFmeiro98ZMMRaDTHAHhJfWkwkw5f2QNIww7T4r85IwbQCgJVRo4m4ZTC +W/1eiEccU2407mECAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +DNhVvGHzKXv0Yh6asK0apP9jJlUwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQCoEVTUY/rF9Zrlpb1Y1hptEguw0i2pCLakcmv3YNj6thsubbGeGx8Z +RjUA/gPKirpoae2HU1y64WEu7akwr6pdTRtXXjbe9NReT6OW/0xAwceSXCOiStqS +cMsWWTGg6BA3uHqad5clqITjDZr1baQ8X8en4SXRBxXyhJXbOkB60HOQeFR9CNeh +pJdrWLeNYXwU0Z59juqdVMGwvDAYdugWUhW2rhafVUXszfRA5c8Izc+E31kq90aY +LmxFXUHUfG0eQOmxmg+Z/nG7yLUdHIFA3id8MRh22hye3KvRdQ7ZVGFni0hG2vQQ +Q01AvD/rhzyjg0czzJKLK9U/RttwdMaV +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBTCCA+2gAwIBAgIRAJfKe4Zh4aWNt3bv6ZjQwogwDQYJKoZIhvcNAQEMBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBjYS1jZW50cmFsLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMDg1M1oYDzIxMjEwNTIxMjMwODUzWjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGNhLWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCpgUH6 +Crzd8cOw9prAh2rkQqAOx2vtuI7xX4tmBG4I/um28eBjyVmgwQ1fpq0Zg2nCKS54 +Nn0pCmT7f3h6Bvopxn0J45AzXEtajFqXf92NQ3iPth95GVfAJSD7gk2LWMhpmID9 +JGQyoGuDPg+hYyr292X6d0madzEktVVGO4mKTF989qEg+tY8+oN0U2fRTrqa2tZp +iYsmg350ynNopvntsJAfpCO/srwpsqHHLNFZ9jvhTU8uW90wgaKO9i31j/mHggCE ++CAOaJCM3g+L8DPl/2QKsb6UkBgaaIwKyRgKSj1IlgrK+OdCBCOgM9jjId4Tqo2j +ZIrrPBGl6fbn1+etZX+2/tf6tegz+yV0HHQRAcKCpaH8AXF44bny9andslBoNjGx +H6R/3ib4FhPrnBMElzZ5i4+eM/cuPC2huZMBXb/jKgRC/QN1Wm3/nah5FWq+yn+N +tiAF10Ga0BYzVhHDEwZzN7gn38bcY5yi/CjDUNpY0OzEe2+dpaBKPlXTaFfn9Nba +CBmXPRF0lLGGtPeTAgjcju+NEcVa82Ht1pqxyu2sDtbu3J5bxp4RKtj+ShwN8nut +Tkf5Ea9rSmHEY13fzgibZlQhXaiFSKA2ASUwgJP19Putm0XKlBCNSGCoECemewxL ++7Y8FszS4Uu4eaIwvXVqUEE2yf+4ex0hqQ1acQIDAQABo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBSeUnXIRxNbYsZLtKomIz4Y1nOZEzAOBgNVHQ8BAf8E +BAMCAYYwDQYJKoZIhvcNAQEMBQADggIBAIpRvxVS0dzoosBh/qw65ghPUGSbP2D4 +dm6oYCv5g/zJr4fR7NzEbHOXX5aOQnHbQL4M/7veuOCLNPOW1uXwywMg6gY+dbKe +YtPVA1as8G9sUyadeXyGh2uXGsziMFXyaESwiAXZyiYyKChS3+g26/7jwECFo5vC +XGhWpIO7Hp35Yglp8AnwnEAo/PnuXgyt2nvyTSrxlEYa0jus6GZEZd77pa82U1JH +qFhIgmKPWWdvELA3+ra1nKnvpWM/xX0pnMznMej5B3RT3Y+k61+kWghJE81Ix78T ++tG4jSotgbaL53BhtQWBD1yzbbilqsGE1/DXPXzHVf9yD73fwh2tGWSaVInKYinr +a4tcrB3KDN/PFq0/w5/21lpZjVFyu/eiPj6DmWDuHW73XnRwZpHo/2OFkei5R7cT +rn/YdDD6c1dYtSw5YNnS6hdCQ3sOiB/xbPRN9VWJa6se79uZ9NLz6RMOr73DNnb2 +bhIR9Gf7XAA5lYKqQk+A+stoKbIT0F65RnkxrXi/6vSiXfCh/bV6B41cf7MY/6YW +ehserSdjhQamv35rTFdM+foJwUKz1QN9n9KZhPxeRmwqPitAV79PloksOnX25ElN +SlyxdndIoA1wia1HRd26EFm2pqfZ2vtD2EjU3wD42CXX4H8fKVDna30nNFSYF0yn +jGKc3k6UNxpg +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQaRHaEqqacXN20e8zZJtmDDANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIHVzLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjIzODM1WhgPMjEyMTA1MjUyMzM4MzVaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgdXMtZWFzdC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAInfBCaHuvj6Rb5c +L5Wmn1jv2PHtEGMHm+7Z8dYosdwouG8VG2A+BCYCZfij9lIGszrTXkY4O7vnXgru +JUNdxh0Q3M83p4X+bg+gODUs3jf+Z3Oeq7nTOk/2UYvQLcxP4FEXILxDInbQFcIx +yen1ESHggGrjEodgn6nbKQNRfIhjhW+TKYaewfsVWH7EF2pfj+cjbJ6njjgZ0/M9 +VZifJFBgat6XUTOf3jwHwkCBh7T6rDpgy19A61laImJCQhdTnHKvzTpxcxiLRh69 +ZObypR7W04OAUmFS88V7IotlPmCL8xf7kwxG+gQfvx31+A9IDMsiTqJ1Cc4fYEKg +bL+Vo+2Ii4W2esCTGVYmHm73drznfeKwL+kmIC/Bq+DrZ+veTqKFYwSkpHRyJCEe +U4Zym6POqQ/4LBSKwDUhWLJIlq99bjKX+hNTJykB+Lbcx0ScOP4IAZQoxmDxGWxN +S+lQj+Cx2pwU3S/7+OxlRndZAX/FKgk7xSMkg88HykUZaZ/ozIiqJqSnGpgXCtED +oQ4OJw5ozAr+/wudOawaMwUWQl5asD8fuy/hl5S1nv9XxIc842QJOtJFxhyeMIXt +LVECVw/dPekhMjS3Zo3wwRgYbnKG7YXXT5WMxJEnHu8+cYpMiRClzq2BEP6/MtI2 +AZQQUFu2yFjRGL2OZA6IYjxnXYiRAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFADCcQCPX2HmkqQcmuHfiQ2jjqnrMA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEASXkGQ2eUmudIKPeOIF7RBryCoPmMOsqP0+1qxF8l +pGkwmrgNDGpmd9s0ArfIVBTc1jmpgB3oiRW9c6n2OmwBKL4UPuQ8O3KwSP0iD2sZ +KMXoMEyphCEzW1I2GRvYDugL3Z9MWrnHkoaoH2l8YyTYvszTvdgxBPpM2x4pSkp+ +76d4/eRpJ5mVuQ93nC+YG0wXCxSq63hX4kyZgPxgCdAA+qgFfKIGyNqUIqWgeyTP +n5OgKaboYk2141Rf2hGMD3/hsGm0rrJh7g3C0ZirPws3eeJfulvAOIy2IZzqHUSY +jkFzraz6LEH3IlArT3jUPvWKqvh2lJWnnp56aqxBR7qHH5voD49UpJWY1K0BjGnS +OHcurpp0Yt/BIs4VZeWdCZwI7JaSeDcPMaMDBvND3Ia5Fga0thgYQTG6dE+N5fgF +z+hRaujXO2nb0LmddVyvE8prYlWRMuYFv+Co8hcMdJ0lEZlfVNu0jbm9/GmwAZ+l +9umeYO9yz/uC7edC8XJBglMAKUmVK9wNtOckUWAcCfnPWYLbYa/PqtXBYcxrso5j +iaS/A7iEW51uteHBGrViCy1afGG+hiUWwFlesli+Rq4dNstX3h6h2baWABaAxEVJ +y1RnTQSz6mROT1VmZSgSVO37rgIyY0Hf0872ogcTS+FfvXgBxCxsNWEbiQ/XXva4 +0Ws= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAMyaTlVLN0ndGp4ffwKAfoMwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBtZS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNTA3MDA0NDM3WhgPMjEyMjA1MDcwMTQ0MzdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgbWUtY2VudHJhbC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE19nCV1nsI6CohSor13+B25cr +zg+IHdi9Y3L7ziQnHWI6yjBazvnKD+oC71aRRlR8b5YXsYGUQxWzPLHN7EGPcSGv +bzA9SLG1KQYCJaQ0m9Eg/iGrwKWOgylbhVw0bCxoo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBS4KsknsJXM9+QPEkBdZxUPaLr11zAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJaRgrYIEfXQMZQQDxMTYS0azpyWSseQooXo +L3nYq4OHGBgYyQ9gVjvRYWU85PXbfgIwdi82DtANQFkCu+j+BU0JBY/uRKPEeYzo +JG92igKIcXPqCoxIJ7lJbbzmuf73gQu5 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAJwCobx0Os8F7ihbJngxrR8wDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjAxNzE1MzNaGA8yMTIxMDUyMDE4MTUzM1owgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBtZS1zb3V0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANukKwlm+ZaI +Y5MkWGbEVLApEyLmlrHLEg8PfiiEa9ts7jssQcin3bzEPdTqGr5jo91ONoZ3ccWq +xJgg1W3bLu5CAO2CqIOXTXHRyCO/u0Ch1FGgWB8xETPSi3UHt/Vn1ltdO6DYdbDU +mYgwzYrvLBdRCwxsb9o+BuYQHVFzUYonqk/y9ujz3gotzFq7r55UwDTA1ita3vb4 +eDKjIb4b1M4Wr81M23WHonpje+9qkkrAkdQcHrkgvSCV046xsq/6NctzwCUUNsgF +7Q1a8ut5qJEYpz5ta8vI1rqFqAMBqCbFjRYlmAoTTpFPOmzAVxV+YoqTrW5A16su +/2SXlMYfJ/n/ad/QfBNPPAAQMpyOr2RCL/YiL/PFZPs7NxYjnZHNWxMLSPgFyI+/ +t2klnn5jR76KJK2qimmaXedB90EtFsMRUU1e4NxH9gDuyrihKPJ3aVnZ35mSipvR +/1KB8t8gtFXp/VQaz2sg8+uxPMKB81O37fL4zz6Mg5K8+aq3ejBiyHucpFGnsnVB +3kQWeD36ONkybngmgWoyPceuSWm1hQ0Z7VRAQX+KlxxSaHmSaIk1XxZu9h9riQHx +fMuev6KXjRn/CjCoUTn+7eFrt0dT5GryQEIZP+nA0oq0LKxogigHNZlwAT4flrqb +JUfZJrqgoce5HjZSXl10APbtPjJi0fW9AgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFEfV+LztI29OVDRm0tqClP3NrmEWMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAvSNe+0wuk53KhWlRlRf2x/97H2Q76X3anzF0 +5fOSVm022ldALzXMzqOfdnoKIhAu2oVKiHHKs7mMas+T6TL+Mkphx0CYEVxFE3PG +061q3CqJU+wMm9W9xsB79oB2XG47r1fIEywZZ3GaRsatAbjcNOT8uBaATPQAfJFN +zjFe4XyN+rA4cFrYNvfHTeu5ftrYmvks7JlRaJgEGWsz+qXux7uvaEEVPqEumd2H +uYeaRNOZ2V23R009X5lbgBFx9tq5VDTnKhQiTQ2SeT0rc1W3Dz5ik6SbQQNP3nSR +0Ywy7r/sZ3fcDyfFiqnrVY4Ympfvb4YW2PZ6OsQJbzH6xjdnTG2HtzEU30ngxdp1 +WUEF4zt6rjJCp7QBUqXgdlHvJqYu6949qtWjEPiFN9uSsRV2i1YDjJqN52dLjAPn +AipJKo8x1PHTwUzuITqnB9BdP+5TlTl8biJfkEf/+08eWDTLlDHr2VrZLOLompTh +bS5OrhDmqA2Q+O+EWrTIhMflwwlCpR9QYM/Xwvlbad9H0FUHbJsCVNaru3wGOgWo +tt3dNSK9Lqnv/Ej9K9v6CRr36in4ylJKivhJ5B9E7ABHg7EpBJ1xi7O5eNDkNoJG ++pFyphJq3AkBR2U4ni2tUaTAtSW2tks7IaiDV+UMtqZyGabT5ISQfWLLtLHSWn2F +Tspdjbg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIECTCCAvGgAwIBAgIRAJZFh4s9aZGzKaTMLrSb4acwDQYJKoZIhvcNAQELBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBCZXRhIHVzLWVhc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTE4MjEyODQxWhgPMjA2MTA1MTgyMjI4NDFa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgQmV0YSB1cy1lYXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +17i2yoU6diep+WrqxIn2CrDEO2NdJVwWTSckx4WMZlLpkQDoymSmkNHjq9ADIApD +A31Cx+843apL7wub8QkFZD0Tk7/ThdHWJOzcAM3ov98QBPQfOC1W5zYIIRP2F+vQ +TRETHQnLcW3rLv0NMk5oQvIKpJoC9ett6aeVrzu+4cU4DZVWYlJUoC/ljWzCluau +8blfW0Vwin6OB7s0HCG5/wijQWJBU5SrP/KAIPeQi1GqG5efbqAXDr/ple0Ipwyo +Xjjl73LenGUgqpANlC9EAT4i7FkJcllLPeK3NcOHjuUG0AccLv1lGsHAxZLgjk/x +z9ZcnVV9UFWZiyJTKxeKPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud +DgQWBBRWyMuZUo4gxCR3Luf9/bd2AqZ7CjAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAIqN2DlIKlvDFPO0QUZQVFbsi/tLdYM98/vvzBpttlTGVMyD +gJuQeHVz+MnhGIwoCGOlGU3OOUoIlLAut0+WG74qYczn43oA2gbMd7HoD7oL/IGg +njorBwJVcuuLv2G//SqM3nxGcLRtkRnQ+lvqPxMz9+0fKFUn6QcIDuF0QSfthLs2 +WSiGEPKO9c9RSXdRQ4pXA7c3hXng8P4A2ZmdciPne5Nu4I4qLDGZYRrRLRkNTrOi +TyS6r2HNGUfgF7eOSeKt3NWL+mNChcYj71/Vycf5edeczpUgfnWy9WbPrK1svKyl +aAs2xg+X6O8qB+Mnj2dNBzm+lZIS3sIlm+nO9sg= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAPAlEk8VJPmEzVRRaWvTh2AwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyB1cy1lYXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI1MjI0MTU1WhgPMjEyMTA1MjUyMzQxNTVaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgdXMtZWFzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEx5xjrup8II4HOJw15NTnS3H5yMrQGlbj +EDA5MMGnE9DmHp5dACIxmPXPMe/99nO7wNdl7G71OYPCgEvWm0FhdvVUeTb3LVnV +BnaXt32Ek7/oxGk1T+Df03C+W0vmuJ+wo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBTGXmqBWN/1tkSea4pNw0oHrjk2UDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAIqqZWCSrIkZ7zsv/FygtAusW6yvlL935YAWYPVXU30m +jkMFLM+/RJ9GMvnO8jHfCgIwB+whlkcItzE9CRQ6CsMo/d5cEHDUu/QW6jSIh9BR +OGh9pTYPVkUbBiKPA7lVVhre +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAJGY9kZITwfSRaAS/bSBOw8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBzYS1lYXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MTEyMFoYDzIxMjEwNTE5MTkxMTIwWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIHNhLWVhc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe2vlDp6Eo4WQi +Wi32YJOgdXHhxTFrLjB9SRy22DYoMaWfginJIwJcSR8yse8ZDQuoNhERB9LRggAE +eng23mhrfvtL1yQkMlZfBu4vG1nOb22XiPFzk7X2wqz/WigdYNBCqa1kK3jrLqPx +YUy7jk2oZle4GLVRTNGuMfcid6S2hs3UCdXfkJuM2z2wc3WUlvHoVNk37v2/jzR/ +hSCHZv5YHAtzL/kLb/e64QkqxKll5QmKhyI6d7vt6Lr1C0zb+DmwxUoJhseAS0hI +dRk5DklMb4Aqpj6KN0ss0HAYqYERGRIQM7KKA4+hxDMUkJmt8KqWKZkAlCZgflzl +m8NZ31o2cvBzf6g+VFHx+6iVrSkohVQydkCxx7NJ743iPKsh8BytSM4qU7xx4OnD +H2yNXcypu+D5bZnVZr4Pywq0w0WqbTM2bpYthG9IC4JeVUvZ2mDc01lqOlbMeyfT +og5BRPLDXdZK8lapo7se2teh64cIfXtCmM2lDSwm1wnH2iSK+AWZVIM3iE45WSGc +vZ+drHfVgjJJ5u1YrMCWNL5C2utFbyF9Obw9ZAwm61MSbPQL9JwznhNlCh7F2ANW +ZHWQPNcOAJqzE4uVcJB1ZeVl28ORYY1668lx+s9yYeMXk3QQdj4xmdnvoBFggqRB +ZR6Z0D7ZohADXe024RzEo1TukrQgKQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBT7Vs4Y5uG/9aXnYGNMEs6ycPUT3jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBACN4Htp2PvGcQA0/sAS+qUVWWJoAXSsu8Pgc6Gar +7tKVlNJ/4W/a6pUV2Xo/Tz3msg4yiE8sMESp2k+USosD5n9Alai5s5qpWDQjrqrh +76AGyF2nzve4kIN19GArYhm4Mz/EKEG1QHYvBDGgXi3kNvL/a2Zbybp+3LevG+q7 +xtx4Sz9yIyMzuT/6Y7ijtiMZ9XbuxGf5wab8UtwT3Xq1UradJy0KCkzRJAz/Wy/X +HbTkEvKSaYKExH6sLo0jqdIjV/d2Io31gt4e0Ly1ER2wPyFa+pc/swu7HCzrN+iz +A2ZM4+KX9nBvFyfkHLix4rALg+WTYJa/dIsObXkdZ3z8qPf5A9PXlULiaa1mcP4+ +rokw74IyLEYooQ8iSOjxumXhnkTS69MAdGzXYE5gnHokABtGD+BB5qLhtLt4fqAp +8AyHpQWMyV42M9SJLzQ+iOz7kAgJOBOaVtJI3FV/iAg/eqWVm3yLuUTWDxSHrKuL +N19+pSjF6TNvUSFXwEa2LJkfDqIOCE32iOuy85QY//3NsgrSQF6UkSPa95eJrSGI +3hTRYYh3Up2GhBGl1KUy7/o0k3KRZTk4s38fylY8bZ3TakUOH5iIGoHyFVVcp361 +Pyy25SzFSmNalWoQd9wZVc/Cps2ldxhcttM+WLkFNzprd0VJa8qTz8vYtHP0ouDN +nWS0 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQKKqVZvk6NsLET+uYv5myCzAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGlsLWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMjEyMDIyMDMyMjBaGA8yMTIyMTIwMjIxMzIyMFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBpbC1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASYwfvj8BmvLAP6UkNQ4X4dXBB/ +webBO7swW+8HnFN2DAu+Cn/lpcDpu+dys1JmkVX435lrCH3oZjol0kCDIM1lF4Cv ++78yoY1Jr/YMat22E4iz4AZd9q0NToS7+ZA0r2yjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFO/8Py16qPr7J2GWpvxlTMB+op7XMA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAwk+rg788+u8JL6sdix7l57WTo8E/M+o3TO5x +uRuPdShrBFm4ArGR2PPs4zCQuKgqAjEAi0TA3PVqAxKpoz+Ps8/054p9WTgDfBFZ +i/lm2yTaPs0xjY6FNWoy7fsVw5oEKxOn +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAOY7gfcBZgR2tqfBzMbFQCUwDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtNCBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjIwNTI1MTY1NDU5WhgPMjEyMjA1MjUxNzU0NTla +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtc291dGhlYXN0LTQgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +lfxER43FuLRdL08bddF0YhbCP+XXKj1A/TFMXmd2My8XDei8rPXFYyyjMig9+xZw +uAsIxLwz8uiA26CKA8bCZKg5VG2kTeOJAfvBJaLv1CZefs3Z4Uf1Sjvm6MF2yqEj +GoORfyfL9HiZFTDuF/hcjWoKYCfMuG6M/wO8IbdICrX3n+BiYQJu/pFO660Mg3h/ +8YBBWYDbHoCiH/vkqqJugQ5BM3OI5nsElW51P1icEEqti4AZ7JmtSv9t7fIFBVyR +oaEyOgpp0sm193F/cDJQdssvjoOnaubsSYm1ep3awZAUyGN/X8MBrPY95d0hLhfH +Ehc5Icyg+hsosBljlAyksmt4hFQ9iBnWIz/ZTfGMck+6p3HVL9RDgvluez+rWv59 +8q7omUGsiPApy5PDdwI/Wt/KtC34/2sjslIJfvgifdAtkRPkhff1WEwER00ADrN9 +eGGInaCpJfb1Rq8cV2n00jxg7DcEd65VR3dmIRb0bL+jWK62ni/WdEyomAOMfmGj +aWf78S/4rasHllWJ+QwnaUYY3u6N8Cgio0/ep4i34FxMXqMV3V0/qXdfhyabi/LM +wCxNo1Dwt+s6OtPJbwO92JL+829QAxydfmaMTeHBsgMPkG7RwAekeuatKGHNsc2Z +x2Q4C2wVvOGAhcHwxfM8JfZs3nDSZJndtVVnFlUY0UECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUpnG7mWazy6k97/tb5iduRB3RXgQwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQCDLqq1Wwa9Tkuv7vxBnIeVvvFF +ecTn+P+wJxl9Qa2ortzqTHZsBDyJO62d04AgBwiDXkJ9a+bthgG0H1J7Xee8xqv1 +xyX2yKj24ygHjspLotKP4eDMdDi5TYq+gdkbPmm9Q69B1+W6e049JVGXvWG8/7kU +igxeuCYwtCCdUPRLf6D8y+1XMGgVv3/DSOHWvTg3MJ1wJ3n3+eve3rjGdRYWZeJu +k21HLSZYzVrCtUsh2YAeLnUbSxVuT2Xr4JehYe9zW5HEQ8Je/OUfnCy9vzoN/ITw +osAH+EBJQey7RxEDqMwCaRefH0yeHFcnOll0OXg/urnQmwbEYzQ1uutJaBPsjU0J +Qf06sMxI7GiB5nPE+CnI2sM6A9AW9kvwexGXpNJiLxF8dvPQthpOKGcYu6BFvRmt +6ctfXd9b7JJoVqMWuf5cCY6ihpk1e9JTlAqu4Eb/7JNyGiGCR40iSLvV28un9wiE +plrdYxwcNYq851BEu3r3AyYWw/UW1AKJ5tM+/Gtok+AphMC9ywT66o/Kfu44mOWm +L3nSLSWEcgfUVgrikpnyGbUnGtgCmHiMlUtNVexcE7OtCIZoVAlCGKNu7tyuJf10 +Qlk8oIIzfSIlcbHpOYoN79FkLoDNc2er4Gd+7w1oPQmdAB0jBJnA6t0OUBPKdDdE +Ufff2jrbfbzECn1ELg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQIuO1A8LOnmc7zZ/vMm3TrDANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA1MjQyMDQ2MThaGA8yMTIxMDUyNDIxNDYxOFow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDq +qRHKbG8ZK6/GkGm2cenznEF06yHwI1gD5sdsHjTgekDZ2Dl9RwtDmUH2zFuIQwGj +SeC7E2iKwrJRA5wYzL9/Vk8NOILEKQOP8OIKUHbc7q8rEtjs401KcU6pFBBEdO9G +CTiRhogq+8mhC13AM/UriZJbKhwgM2UaDOzAneGMhQAGjH8z83NsNcPxpYVE7tqM +sch5yLtIJLkJRusrmQQTeHUev16YNqyUa+LuFclFL0FzFCimkcxUhXlbfEKXbssS +yPzjiv8wokGyo7+gA0SueceMO2UjfGfute3HlXZDcNvBbkSY+ver41jPydyRD6Qq +oEkh0tyIbPoa3oU74kwipJtz6KBEA3u3iq61OUR0ENhR2NeP7CSKrC24SnQJZ/92 +qxusrbyV/0w+U4m62ug/o4hWNK1lUcc2AqiBOvCSJ7qpdteTFxcEIzDwYfERDx6a +d9+3IPvzMb0ZCxBIIUFMxLTF7yAxI9s6KZBBXSZ6tDcCCYIgEysEPRWMRAcG+ye/ +fZVn9Vnzsj4/2wchC2eQrYpb1QvG4eMXA4M5tFHKi+/8cOPiUzJRgwS222J8YuDj +yEBval874OzXk8H8Mj0JXJ/jH66WuxcBbh5K7Rp5oJn7yju9yqX6qubY8gVeMZ1i +u4oXCopefDqa35JplQNUXbWwSebi0qJ4EK0V8F9Q+QIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBT4ysqCxaPe7y+g1KUIAenqu8PAgzAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBALU8WN35KAjPZEX65tobtCDQFkIO +uJjv0alD7qLB0i9eY80C+kD87HKqdMDJv50a5fZdqOta8BrHutgFtDm+xo5F/1M3 +u5/Vva5lV4xy5DqPajcF4Mw52czYBmeiLRTnyPJsU93EQIC2Bp4Egvb6LI4cMOgm +4pY2hL8DojOC5PXt4B1/7c1DNcJX3CMzHDm4SMwiv2MAxSuC/cbHXcWMk+qXdrVx ++ayLUSh8acaAOy3KLs1MVExJ6j9iFIGsDVsO4vr4ZNsYQiyHjp+L8ops6YVBO5AT +k/pI+axHIVsO5qiD4cFWvkGqmZ0gsVtgGUchZaacboyFsVmo6QPrl28l6LwxkIEv +GGJYvIBW8sfqtGRspjfX5TlNy5IgW/VOwGBdHHsvg/xpRo31PR3HOFw7uPBi7cAr +FiZRLJut7af98EB2UvovZnOh7uIEGPeecQWeOTQfJeWet2FqTzFYd0NUMgqPuJx1 +vLKferP+ajAZLJvVnW1J7Vccx/pm0rMiUJEf0LRb/6XFxx7T2RGjJTi0EzXODTYI +gnLfBBjnolQqw+emf4pJ4pAtly0Gq1KoxTG2QN+wTd4lsCMjnelklFDjejwnl7Uy +vtxzRBAu/hi/AqDkDFf94m6j+edIrjbi9/JDFtQ9EDlyeqPgw0qwi2fwtJyMD45V +fejbXelUSJSzDIdY +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCTCCA/GgAwIBAgIRAN7Y9G9i4I+ZaslPobE7VL4wDQYJKoZIhvcNAQEMBQAw +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1ub3J0aGVhc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwIBcNMjEwNTIwMTYzMzIzWhgPMjEyMTA1MjAxNzMzMjNa +MIGcMQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywg +SW5jLjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExNTAzBgNVBAMM +LEFtYXpvbiBSRFMgYXAtbm9ydGhlYXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAw +DgYDVQQHDAdTZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA +4BEPCiIfiK66Q/qa8k+eqf1Q3qsa6Xuu/fPkpuStXVBShhtXd3eqrM0iT4Xxs420 +Va0vSB3oZ7l86P9zYfa60n6PzRxdYFckYX330aI7L/oFIdaodB/C9szvROI0oLG+ +6RwmIF2zcprH0cTby8MiM7G3v9ykpq27g4WhDC1if2j8giOQL3oHpUaByekZNIHF +dIllsI3RkXmR3xmmxoOxJM1B9MZi7e1CvuVtTGOnSGpNCQiqofehTGwxCN2wFSK8 +xysaWlw48G0VzZs7cbxoXMH9QbMpb4tpk0d+T8JfAPu6uWO9UwCLWWydf0CkmA/+ +D50/xd1t33X9P4FEaPSg5lYbHXzSLWn7oLbrN2UqMLaQrkoEBg/VGvzmfN0mbflw ++T87bJ/VEOVNlG+gepyCTf89qIQVWOjuYMox4sK0PjzZGsYEuYiq1+OUT3vk/e5K +ag1fCcq2Isy4/iwB2xcXrsQ6ljwdk1fc+EmOnjGKrhuOHJY3S+RFv4ToQBsVyYhC +XGaC3EkqIX0xaCpDimxYhFjWhpDXAjG/zJ+hRLDAMCMhl/LPGRk/D1kzSbPmdjpl +lEMK5695PeBvEBTQdBQdOiYgOU3vWU6tzwwHfiM2/wgvess/q0FDAHfJhppbgbb9 +3vgsIUcsvoC5o29JvMsUxsDRvsAfEmMSDGkJoA/X6GECAwEAAaNCMEAwDwYDVR0T +AQH/BAUwAwEB/zAdBgNVHQ4EFgQUgEWm1mZCbGD6ytbwk2UU1aLaOUUwDgYDVR0P +AQH/BAQDAgGGMA0GCSqGSIb3DQEBDAUAA4ICAQBb4+ABTGBGwxK1U/q4g8JDqTQM +1Wh8Oz8yAk4XtPJMAmCctxbd81cRnSnePWw/hxViLVtkZ/GsemvXfqAQyOn1coN7 +QeYSw+ZOlu0j2jEJVynmgsR7nIRqE7QkCyZAU+d2FTJUfmee+IiBiGyFGgxz9n7A +JhBZ/eahBbiuoOik/APW2JWLh0xp0W0GznfJ8lAlaQTyDa8iDXmVtbJg9P9qzkvl +FgPXQttzEOyooF8Pb2LCZO4kUz+1sbU7tHdr2YE+SXxt6D3SBv+Yf0FlvyWLiqVk +GDEOlPPTDSjAWgKnqST8UJ0RDcZK/v1ixs7ayqQJU0GUQm1I7LGTErWXHMnCuHKe +UKYuiSZwmTcJ06NgdhcCnGZgPq13ryMDqxPeltQc3n5eO7f1cL9ERYLDLOzm6A9P +oQ3MfcVOsbHgGHZWaPSeNrQRN9xefqBXH0ZPasgcH9WJdsLlEjVUXoultaHOKx3b +UCCb+d3EfqF6pRT488ippOL6bk7zNubwhRa/+y4wjZtwe3kAX78ACJVcjPobH9jZ +ErySads5zdQeaoee5wRKdp3TOfvuCe4bwLRdhOLCHWzEcXzY3g/6+ppLvNom8o+h +Bh5X26G6KSfr9tqhQ3O9IcbARjnuPbvtJnoPY0gz3EHHGPhy0RNW8i2gl3nUp0ah +PtjwbKW0hYAhIttT0Q== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtzCCAj2gAwIBAgIQQRBQTs6Y3H1DDbpHGta3lzAKBggqhkjOPQQDAzCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDYxMTAwMTI0M1oYDzIxMjEwNjExMDExMjQzWjCBmzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTQwMgYDVQQDDCtBbWF6 +b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEs0942Xj4m/gKA+WA6F5h +AHYuek9eGpzTRoLJddM4rEV1T3eSueytMVKOSlS3Ub9IhyQrH2D8EHsLYk9ktnGR +pATk0kCYTqFbB7onNo070lmMJmGT/Q7NgwC8cySChFxbo0IwQDAPBgNVHRMBAf8E +BTADAQH/MB0GA1UdDgQWBBQ20iKBKiNkcbIZRu0y1uoF1yJTEzAOBgNVHQ8BAf8E +BAMCAYYwCgYIKoZIzj0EAwMDaAAwZQIwYv0wTSrpQTaPaarfLN8Xcqrqu3hzl07n +FrESIoRw6Cx77ZscFi2/MV6AFyjCV/TlAjEAhpwJ3tpzPXpThRML8DMJYZ3YgMh3 +CMuLqhPpla3cL0PhybrD27hJWl29C4el6aMO +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrDCCAjOgAwIBAgIQGcztRyV40pyMKbNeSN+vXTAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIHVzLWVhc3QtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjEyMzE1NTZaGA8yMTIxMDUyMjAwMTU1NlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyB1cy1lYXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfDcv+GGRESD9wT+I5YIPRsD3L+/jsiIis +Tr7t9RSbFl+gYpO7ZbDXvNbV5UGOC5lMJo/SnqFRTC6vL06NF7qOHfig3XO8QnQz +6T5uhhrhnX2RSY3/10d2kTyHq3ZZg3+jQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFLDyD3PRyNXpvKHPYYxjHXWOgfPnMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNnADBkAjB20HQp6YL7CqYD82KaLGzgw305aUKw2aMrdkBR29J183jY +6Ocj9+Wcif9xnRMS+7oCMAvrt03rbh4SU9BohpRUcQ2Pjkh7RoY0jDR4Xq4qzjNr +5UFr3BXpFvACxXF51BksGQ== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjWgAwIBAgIQeKbS5zvtqDvRtwr5H48cAjAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIG1lLXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTcxOTU1WhgPMjEyMTA1MjAxODE5NTVaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgbWUtc291dGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABEKjgUaAPmUlRMEQdBC7BScAGosJ1zRV +LDd38qTBjzgmwBfQJ5ZfGIvyEK5unB09MB4e/3qqK5I/L6Qn5Px/n5g4dq0c7MQZ +u7G9GBYm90U3WRJBf7lQrPStXaRnS4A/O6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUNKcAbGEIn03/vkwd8g6jNyiRdD4wDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2cAMGQCMHIeTrjenCSYuGC6txuBt/0ZwnM/ciO9kHGWVCoK8QLs +jGghb5/YSFGZbmQ6qpGlSAIwVOQgdFfTpEfe5i+Vs9frLJ4QKAfc27cTNYzRIM0I +E+AJgK4C4+DiyyMzOpiCfmvq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGCDCCA/CgAwIBAgIQSFkEUzu9FYgC5dW+5lnTgjANBgkqhkiG9w0BAQwFADCB +nDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTUwMwYDVQQDDCxB +bWF6b24gUkRTIGFwLXNvdXRoZWFzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4G +A1UEBwwHU2VhdHRsZTAgFw0yMTA2MTEwMDA4MzZaGA8yMTIxMDYxMTAxMDgzNlow +gZwxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTE1MDMGA1UEAwws +QW1hem9uIFJEUyBhcC1zb3V0aGVhc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAO +BgNVBAcMB1NlYXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDx +my5Qmd8zdwaI/KOKV9Xar9oNbhJP5ED0JCiigkuvCkg5qM36klszE8JhsUj40xpp +vQw9wkYW4y+C8twBpzKGBvakqMnoaVUV7lOCKx0RofrnNwkZCboTBB4X/GCZ3fIl +YTybS7Ehi1UuiaZspIT5A2jidoA8HiBPk+mTg1UUkoWS9h+MEAPa8L4DY6fGf4pO +J1Gk2cdePuNzzIrpm2yPto+I8MRROwZ3ha7ooyymOXKtz2c7jEHHJ314boCXAv9G +cdo27WiebewZkHHH7Zx9iTIVuuk2abyVSzvLVeGv7Nuy4lmSqa5clWYqWsGXxvZ2 +0fZC5Gd+BDUMW1eSpW7QDTk3top6x/coNoWuLSfXiC5ZrJkIKimSp9iguULgpK7G +abMMN4PR+O+vhcB8E879hcwmS2yd3IwcPTl3QXxufqeSV58/h2ibkqb/W4Bvggf6 +5JMHQPlPHOqMCVFIHP1IffIo+Of7clb30g9FD2j3F4qgV3OLwEDNg/zuO1DiAvH1 +L+OnmGHkfbtYz+AVApkAZrxMWwoYrwpauyBusvSzwRE24vLTd2i80ZDH422QBLXG +rN7Zas8rwIiBKacJLYtBYETw8mfsNt8gb72aIQX6cZOsphqp6hUtKaiMTVgGazl7 +tBXqbB+sIv3S9X6bM4cZJKkMJOXbnyCCLZFYv8TurwIDAQABo0IwQDAPBgNVHRMB +Af8EBTADAQH/MB0GA1UdDgQWBBTOVtaS1b/lz6yJDvNk65vEastbQTAOBgNVHQ8B +Af8EBAMCAYYwDQYJKoZIhvcNAQEMBQADggIBABEONg+TmMZM/PrYGNAfB4S41zp1 +3CVjslZswh/pC4kgXSf8cPJiUOzMwUevuFQj7tCqxQtJEygJM2IFg4ViInIah2kh +xlRakEGGw2dEVlxZAmmLWxlL1s1lN1565t5kgVwM0GVfwYM2xEvUaby6KDVJIkD3 +aM6sFDBshvVA70qOggM6kU6mwTbivOROzfoIQDnVaT+LQjHqY/T+ok6IN0YXXCWl +Favai8RDjzLDFwXSRvgIK+1c49vlFFY4W9Efp7Z9tPSZU1TvWUcKdAtV8P2fPHAS +vAZ+g9JuNfeawhEibjXkwg6Z/yFUueQCQOs9TRXYogzp5CMMkfdNJF8byKYqHscs +UosIcETnHwqwban99u35sWcoDZPr6aBIrz7LGKTJrL8Nis8qHqnqQBXu/fsQEN8u +zJ2LBi8sievnzd0qI0kaWmg8GzZmYH1JCt1GXSqOFkI8FMy2bahP7TUQR1LBUKQ3 +hrOSqldkhN+cSAOnvbQcFzLr+iEYEk34+NhcMIFVE+51KJ1n6+zISOinr6mI3ckX +6p2tmiCD4Shk2Xx/VTY/KGvQWKFcQApWezBSvDNlGe0yV71LtLf3dr1pr4ofo7cE +rYucCJ40bfxEU/fmzYdBF32xP7AOD9U0FbOR3Mcthc6Z6w20WFC+zru8FGY08gPf +WT1QcNdw7ntUJP/w +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQARky6+5PNFRkFVOp3Ob1CTAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjIwNTIzMTg0MTI4WhgPMjEyMjA1MjMxOTQxMjdaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtc291dGgtMiBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABNVGL5oF7cfIBxKyWd2PVK/S5yQfaJY3 +QFHWvEdt6951n9JhiiPrHzfVHsxZp1CBjILRMzjgRbYWmc8qRoLkgGE7htGdwudJ +Fa/WuKzO574Prv4iZXUnVGTboC7JdvKbh6NCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUgDeIIEKynwUbNXApdIPnmRWieZwwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMEOOJfucrST+FxuqJkMZyCM3gWGZaB+/w6+XUAJC6hFM +uSTY0F44/bERkA4XhH+YGAIxAIpJQBakCA1/mXjsTnQ+0El9ty+LODp8ibkn031c +8DKDS7pR9UK7ZYdR6zFg3ZCjQw== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjOgAwIBAgIQJvkWUcYLbnxtuwnyjMmntDAKBggqhkjOPQQDAzCBljEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMS8wLQYDVQQDDCZBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTAgFw0yMTA1MjUyMjI2MTJaGA8yMTIxMDUyNTIzMjYxMlowgZYxCzAJBgNV +BAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMwEQYD +VQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1hem9uIFJE +UyBldS13ZXN0LTMgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0bGUw +djAQBgcqhkjOPQIBBgUrgQQAIgNiAARENn8uHCyjn1dFax4OeXxvbV861qsXFD9G +DshumTmFzWWHN/69WN/AOsxy9XN5S7Cgad4gQgeYYYgZ5taw+tFo/jQvCLY//uR5 +uihcLuLJ78opvRPvD9kbWZ6oXfBtFkWjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYD +VR0OBBYEFKiK3LpoF+gDnqPldGSwChBPCYciMA4GA1UdDwEB/wQEAwIBhjAKBggq +hkjOPQQDAwNpADBmAjEA+7qfvRlnvF1Aosyp9HzxxCbN7VKu+QXXPhLEBWa5oeWW +UOcifunf/IVLC4/FGCsLAjEAte1AYp+iJyOHDB8UYkhBE/1sxnFaTiEPbvQBU0wZ +SuwWVLhu2wWDuSW+K7tTuL8p +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAKeDpqX5WFCGNo94M4v69sUwDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTMgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNTIyMTgzM1oYDzIwNjEwNTI1MjMxODMzWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCcKOTEMTfzvs4H +WtJR8gI7GXN6xesulWtZPv21oT+fLGwJ+9Bv8ADCGDDrDxfeH/HxJmzG9hgVAzVn +4g97Bn7q07tGZM5pVi96/aNp11velZT7spOJKfJDZTlGns6DPdHmx48whpdO+dOb +6+eR0VwCIv+Vl1fWXgoACXYCoKjhxJs+R+fwY//0JJ1YG8yjZ+ghLCJmvlkOJmE1 +TCPUyIENaEONd6T+FHGLVYRRxC2cPO65Jc4yQjsXvvQypoGgx7FwD5voNJnFMdyY +754JGPOOe/SZdepN7Tz7UEq8kn7NQSbhmCsgA/Hkjkchz96qN/YJ+H/okiQUTNB0 +eG9ogiVFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjayw9Y +MjbxfF14XAhMM2VPl0PfMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAAtmx6d9+9CWlMoU0JCirtp4dSS41bBfb9Oor6GQ8WIr2LdfZLL6uES/ubJPE +1Sh5Vu/Zon5/MbqLMVrfniv3UpQIof37jKXsjZJFE1JVD/qQfRzG8AlBkYgHNEiS +VtD4lFxERmaCkY1tjKB4Dbd5hfhdrDy29618ZjbSP7NwAfnwb96jobCmMKgxVGiH +UqsLSiEBZ33b2hI7PJ6iTJnYBWGuiDnsWzKRmheA4nxwbmcQSfjbrNwa93w3caL2 +v/4u54Kcasvcu3yFsUwJygt8z43jsGAemNZsS7GWESxVVlW93MJRn6M+MMakkl9L +tWaXdHZ+KUV7LhfYLb0ajvb40w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBDCCAuygAwIBAgIQJ5oxPEjefCsaESSwrxk68DANBgkqhkiG9w0BAQsFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjExNzA1WhgPMjA2MjA2MDYyMjE3MDVaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALTQt5eX +g+VP3BjO9VBkWJhE0GfLrU/QIk32I6WvrnejayTrlup9H1z4QWlXF7GNJrqScRMY +KhJHlcP05aPsx1lYco6pdFOf42ybXyWHHJdShj4A5glU81GTT+VrXGzHSarLmtua +eozkQgPpDsSlPt0RefyTyel7r3Cq+5K/4vyjCTcIqbfgaGwTU36ffjM1LaPCuE4O +nINMeD6YuImt2hU/mFl20FZ+IZQUIFZZU7pxGLqTRz/PWcH8tDDxnkYg7tNuXOeN +JbTpXrw7St50/E9ZQ0llGS+MxJD8jGRAa/oL4G/cwnV8P2OEPVVkgN9xDDQeieo0 +3xkzolkDkmeKOnUCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQU +bwu8635iQGQMRanekesORM8Hkm4wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +CwUAA4IBAQAgN6LE9mUgjsj6xGCX1afYE69fnmCjjb0rC6eEe1mb/QZNcyw4XBIW +6+zTXo4mjZ4ffoxb//R0/+vdTE7IvaLgfAZgFsLKJCtYDDstXZj8ujQnGR9Pig3R +W+LpNacvOOSJSawNQq0Xrlcu55AU4buyD5VjcICnfF1dqBMnGTnh27m/scd/ZMx/ +kapHZ/fMoK2mAgSX/NvUKF3UkhT85vSSM2BTtET33DzCPDQTZQYxFBa4rFRmFi4c +BLlmIReiCGyh3eJhuUUuYAbK6wLaRyPsyEcIOLMQmZe1+gAFm1+1/q5Ke9ugBmjf +PbTWjsi/lfZ5CdVAhc5lmZj/l5aKqwaS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAKKPTYKln9L4NTx9dpZGUjowCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIxMjI1NTIxWhgPMjEyMTA1MjEyMzU1MjFaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/owTReDvaRqdmbtTzXbyRmEpKCETNj6O +hZMKH0F8oU9Tmn8RU7kQQj6xUKEyjLPrFBN7c+26TvrVO1KmJAvbc8bVliiJZMbc +C0yV5PtJTalvlMZA1NnciZuhxaxrzlK1o0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBT4i5HaoHtrs7Mi8auLhMbKM1XevDAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAK9A+8/lFdX4XJKgfP+ZLy5ySXC2E0Spoy12Gv2GdUEZ +p1G7c1KbWVlyb1d6subzkQIwKyH0Naf/3usWfftkmq8SzagicKz5cGcEUaULq4tO +GzA/AMpr63IDBAqkZbMDTCmH +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrzCCAjWgAwIBAgIQTgIvwTDuNWQo0Oe1sOPQEzAKBggqhkjOPQQDAzCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTI0MjEwNjM4WhgPMjEyMTA1MjQyMjA2MzhaMIGXMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpvbiBS +RFMgZXUtbm9ydGgtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwHU2VhdHRs +ZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABJuzXLU8q6WwSKXBvx8BbdIi3mPhb7Xo +rNJBfuMW1XRj5BcKH1ZoGaDGw+BIIwyBJg8qNmCK8kqIb4cH8/Hbo3Y+xBJyoXq/ +cuk8aPrxiNoRsKWwiDHCsVxaK9L7GhHHAqNCMEAwDwYDVR0TAQH/BAUwAwEB/zAd +BgNVHQ4EFgQUYgcsdU4fm5xtuqLNppkfTHM2QMYwDgYDVR0PAQH/BAQDAgGGMAoG +CCqGSM49BAMDA2gAMGUCMQDz/Rm89+QJOWJecYAmYcBWCcETASyoK1kbr4vw7Hsg +7Ew3LpLeq4IRmTyuiTMl0gMCMAa0QSjfAnxBKGhAnYxcNJSntUyyMpaXzur43ec0 +3D8npJghwC4DuICtKEkQiI5cSg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAORIGqQXLTcbbYT2upIsSnQwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMjA1MjMxODM0MjJaGA8yMTIyMDUyMzE5MzQyMlowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBldS1zb3V0aC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAPKukwsW2s/h +1k+Hf65pOP0knVBnOnMQyT1mopp2XHGdXznj9xS49S30jYoUnWccyXgD983A1bzu +w4fuJRHg4MFdz/NWTgXvy+zy0Roe83OPIJjUmXnnzwUHQcBa9vl6XUO65iQ3pbSi +fQfNDFXD8cvuXbkezeADoy+iFAlzhXTzV9MD44GTuo9Z3qAXNGHQCrgRSCL7uRYt +t1nfwboCbsVRnElopn2cTigyVXE62HzBUmAw1GTbAZeFAqCn5giBWYAfHwTUldRL +6eEa6atfsS2oPNus4ZENa1iQxXq7ft+pMdNt0qKXTCZiiCZjmLkY0V9kWwHTRRF8 +r+75oSL//3di43QnuSCgjwMRIeWNtMud5jf3eQzSBci+9njb6DrrSUbx7blP0srg +94/C/fYOp/0/EHH34w99Th14VVuGWgDgKahT9/COychLOubXUT6vD1As47S9KxTv +yYleVKwJnF9cVjepODN72fNlEf74BwzgSIhUmhksmZSeJBabrjSUj3pdyo/iRZN/ +CiYz9YPQ29eXHPQjBZVIUqWbOVfdwsx0/Xu5T1e7yyXByQ3/oDulahtcoKPAFQ3J +ee6NJK655MdS7pM9hJnU2Rzu3qZ/GkM6YK7xTlMXVouPUZov/VbiaCKbqYDs8Dg+ +UKdeNXAT6+BMleGQzly1X7vjhgeA8ugVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFJdaPwpCf78UolFTEn6GO85/QwUIMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAWkxHIT3mers5YnZRSVjmpxCLivGj1jMB9VYC +iKqTAeIvD0940L0YaZgivQll5pue8UUcQ6M2uCdVVAsNJdmQ5XHIYiGOknYPtxzO +aO+bnZp7VIZw/vJ49hvH6RreA2bbxYMZO/ossYdcWsWbOKHFrRmAw0AhtK/my51g +obV7eQg+WmlE5Iqc75ycUsoZdc3NimkjBi7LQoNP1HMvlLHlF71UZhQDdq+/WdV7 +0zmg+epkki1LjgMmuPyb+xWuYkFKT1/faX+Xs62hIm5BY+aI4if4RuQ+J//0pOSs +UajrjTo+jLGB8A96jAe8HaFQenbwMjlaHRDAF0wvbkYrMr5a6EbneAB37V05QD0Y +Rh4L4RrSs9DX2hbSmS6iLDuPEjanHKzglF5ePEvnItbRvGGkynqDVlwF+Bqfnw8l +0i8Hr1f1/LP1c075UjkvsHlUnGgPbLqA0rDdcxF8Fdlv1BunUjX0pVlz10Ha5M6P +AdyWUOneOfaA5G7jjv7i9qg3r99JNs1/Lmyg/tV++gnWTAsSPFSSEte81kmPhlK3 +2UtAO47nOdTtk+q4VIRAwY1MaOR7wTFZPfer1mWs4RhKNu/odp8urEY87iIzbMWT +QYO/4I6BGj9rEWNGncvR5XTowwIthMCj2KWKM3Z/JxvjVFylSf+s+FFfO1bNIm6h +u3UBpZI= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjmgAwIBAgIQenQbcP/Zbj9JxvZ+jXbRnTAKBggqhkjOPQQDAzCBmTEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTIwMAYDVQQDDClBbWF6 +b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIEVDQzM4NCBHMTEQMA4GA1UEBwwH +U2VhdHRsZTAgFw0yMTA1MjEyMjMzMjRaGA8yMTIxMDUyMTIzMzMyNFowgZkxCzAJ +BgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMuMRMw +EQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1hem9u +IFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATlBHiEM9LoEb1Hdnd5j2VpCDOU +5nGuFoBD8ROUCkFLFh5mHrHfPXwBc63heW9WrP3qnDEm+UZEUvW7ROvtWCTPZdLz +Z4XaqgAlSqeE2VfUyZOZzBSgUUJk7OlznXfkCMOjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFDT/ThjQZl42Nv/4Z/7JYaPNMly2MA4GA1UdDwEB/wQEAwIB +hjAKBggqhkjOPQQDAwNpADBmAjEAnZWmSgpEbmq+oiCa13l5aGmxSlfp9h12Orvw +Dq/W5cENJz891QD0ufOsic5oGq1JAjEAp5kSJj0MxJBTHQze1Aa9gG4sjHBxXn98 +4MP1VGsQuhfndNHQb4V0Au7OWnOeiobq +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/zCCAuegAwIBAgIRAMgnyikWz46xY6yRgiYwZ3swDQYJKoZIhvcNAQELBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NDkxMloYDzIwNjEwNTIwMTc0OTEyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCi8JYOc9cYSgZH +gYPxLk6Xcc7HqzamvsnjYU98Dcb98y6iDqS46Ra2Ne02MITtU5MDL+qjxb8WGDZV +RUA9ZS69tkTO3gldW8QdiSh3J6hVNJQW81F0M7ZWgV0gB3n76WCmfT4IWos0AXHM +5v7M/M4tqVmCPViQnZb2kdVlM3/Xc9GInfSMCgNfwHPTXl+PXX+xCdNBePaP/A5C +5S0oK3HiXaKGQAy3K7VnaQaYdiv32XUatlM4K2WS4AMKt+2cw3hTCjlmqKRHvYFQ +veWCXAuc+U5PQDJ9SuxB1buFJZhT4VP3JagOuZbh5NWpIbOTxlAJOb5pGEDuJTKi +1gQQQVEFAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFNXm+N87 +OFxK9Af/bjSxDCiulGUzMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0BAQsFAAOC +AQEAkqIbkgZ45spvrgRQ6n9VKzDLvNg+WciLtmVrqyohwwJbj4pYvWwnKQCkVc7c +hUOSBmlSBa5REAPbH5o8bdt00FPRrD6BdXLXhaECKgjsHe1WW08nsequRKD8xVmc +8bEX6sw/utBeBV3mB+3Zv7ejYAbDFM4vnRsWtO+XqgReOgrl+cwdA6SNQT9oW3e5 +rSQ+VaXgJtl9NhkiIysq9BeYigxqS/A13pHQp0COMwS8nz+kBPHhJTsajHCDc8F4 +HfLi6cgs9G0gaRhT8FCH66OdGSqn196sE7Y3bPFFFs/3U+vxvmQgoZC6jegQXAg5 +Prxd+VNXtNI/azitTysQPumH7A== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEBTCCAu2gAwIBAgIRAO8bekN7rUReuNPG8pSTKtEwDQYJKoZIhvcNAQELBQAw +gZoxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEzMDEGA1UEAwwq +QW1hem9uIFJEUyBldS1jZW50cmFsLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYD +VQQHDAdTZWF0dGxlMCAXDTIxMDUyMTIyMjM0N1oYDzIwNjEwNTIxMjMyMzQ3WjCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTIwNDggRzExEDAOBgNV +BAcMB1NlYXR0bGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCTTYds +Tray+Q9VA5j5jTh5TunHKFQzn68ZbOzdqaoi/Rq4ohfC0xdLrxCpfqn2TGDHN6Zi +2qGK1tWJZEd1H0trhzd9d1CtGK+3cjabUmz/TjSW/qBar7e9MA67/iJ74Gc+Ww43 +A0xPNIWcL4aLrHaLm7sHgAO2UCKsrBUpxErOAACERScVYwPAfu79xeFcX7DmcX+e +lIqY16pQAvK2RIzrekSYfLFxwFq2hnlgKHaVgZ3keKP+nmXcXmRSHQYUUr72oYNZ +HcNYl2+gxCc9ccPEHM7xncVEKmb5cWEWvVoaysgQ+osi5f5aQdzgC2X2g2daKbyA +XL/z5FM9GHpS5BJjAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE +FBDAiJ7Py9/A9etNa/ebOnx5l5MGMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG9w0B +AQsFAAOCAQEALMh/+81fFPdJV/RrJUeoUvFCGMp8iaANu97NpeJyKitNOv7RoeVP +WjivS0KcCqZaDBs+p6IZ0sLI5ZH098LDzzytcfZg0PsGqUAb8a0MiU/LfgDCI9Ee +jsOiwaFB8k0tfUJK32NPcIoQYApTMT2e26lPzYORSkfuntme2PTHUnuC7ikiQrZk +P+SZjWgRuMcp09JfRXyAYWIuix4Gy0eZ4rpRuaTK6mjAb1/LYoNK/iZ/gTeIqrNt +l70OWRsWW8jEmSyNTIubGK/gGGyfuZGSyqoRX6OKHESkP6SSulbIZHyJ5VZkgtXo +2XvyRyJ7w5pFyoofrL3Wv0UF8yt/GDszmg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAMDk/F+rrhdn42SfE+ghPC8wDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTIgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMTIyNTEyMloYDzIxMjEwNTIxMjM1MTIyWjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2twMALVg9vRVu +VNqsr6N8thmp3Dy8jEGTsm3GCQ+C5P2YcGlD/T/5icfWW84uF7Sx3ezcGlvsqFMf +Ukj9sQyqtz7qfFFugyy7pa/eH9f48kWFHLbQYm9GEgbYBIrWMp1cy3vyxuMCwQN4 +DCncqU+yNpy0CprQJEha3PzY+3yJOjDQtc3zr99lyECCFJTDUucxHzyQvX89eL74 +uh8la0lKH3v9wPpnEoftbrwmm5jHNFdzj7uXUHUJ41N7af7z7QUfghIRhlBDiKtx +5lYZemPCXajTc3ryDKUZC/b+B6ViXZmAeMdmQoPE0jwyEp/uaUcdp+FlUQwCfsBk +ayPFEApTWgPiku2isjdeTVmEgL8bJTDUZ6FYFR7ZHcYAsDzcwHgIu3GGEMVRS3Uf +ILmioiyly9vcK4Sa01ondARmsi/I0s7pWpKflaekyv5boJKD/xqwz9lGejmJHelf +8Od2TyqJScMpB7Q8c2ROxBwqwB72jMCEvYigB+Wnbb8RipliqNflIGx938FRCzKL +UQUBmNAznR/yRRL0wHf9UAE/8v9a09uZABeiznzOFAl/frHpgdAbC00LkFlnwwgX +g8YfEFlkp4fLx5B7LtoO6uVNFVimLxtwirpyKoj3G4M/kvSTux8bTw0heBCmWmKR +57MS6k7ODzbv+Kpeht2hqVZCNFMxoQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBRuMnDhJjoj7DcKALj+HbxEqj3r6jAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBALSnXfx72C3ldhBP5kY4Mo2DDaGQ8FGpTOOiD95d +0rf7I9LrsBGVqu/Nir+kqqP80PB70+Jy9fHFFigXwcPBX3MpKGxK8Cel7kVf8t1B +4YD6A6bqlzP+OUL0uGWfZpdpDxwMDI2Flt4NEldHgXWPjvN1VblEKs0+kPnKowyg +jhRMgBbD/y+8yg0fIcjXUDTAw/+INcp21gWaMukKQr/8HswqC1yoqW9in2ijQkpK +2RB9vcQ0/gXR0oJUbZQx0jn0OH8Agt7yfMAnJAdnHO4M3gjvlJLzIC5/4aGrRXZl +JoZKfJ2fZRnrFMi0nhAYDeInoS+Rwx+QzaBk6fX5VPyCj8foZ0nmqvuYoydzD8W5 +mMlycgxFqS+DUmO+liWllQC4/MnVBlHGB1Cu3wTj5kgOvNs/k+FW3GXGzD3+rpv0 +QTLuwSbMr+MbEThxrSZRSXTCQzKfehyC+WZejgLb+8ylLJUA10e62o7H9PvCrwj+ +ZDVmN7qj6amzvndCP98sZfX7CFZPLfcBd4wVIjHsFjSNEwWHOiFyLPPG7cdolGKA +lOFvonvo4A1uRc13/zFeP0Xi5n5OZ2go8aOOeGYdI2vB2sgH9R2IASH/jHmr0gvY +0dfBCcfXNgrS0toq0LX/y+5KkKOxh52vEYsJLdhqrveuZhQnsFEm/mFwjRXkyO7c +2jpC +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGADCCA+igAwIBAgIQYe0HgSuFFP9ivYM2vONTrTANBgkqhkiG9w0BAQwFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MzMyMVoYDzIxMjEwNTE5MTkzMzIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuO7QPKfPMTo2 +POQWvzDLwi5f++X98hGjORI1zkN9kotCYH5pAzSBwBPoMNaIfedgmsIxGHj2fq5G +4oXagNhNuGP79Zl6uKW5H7S74W7aWM8C0s8zuxMOI4GZy5h2IfQk3m/3AzZEX5w8 +UtNPkzo2feDVOkerHT+j+vjXgAxZ4wHnuMDcRT+K4r9EXlAH6X9b/RO0JlfEwmNz +xlqqGxocq9qRC66N6W0HF2fNEAKP84n8H80xcZBOBthQORRi8HSmKcPdmrvwCuPz +M+L+j18q6RAVaA0ABbD0jMWcTf0UvjUfBStn5mvu/wGlLjmmRkZsppUTRukfwqXK +yltUsTq0tOIgCIpne5zA4v+MebbR5JBnsvd4gdh5BI01QH470yB7BkUefZ9bobOm +OseAAVXcYFJKe4DAA6uLDrqOfFSxV+CzVvEp3IhLRaik4G5MwI/h2c/jEYDqkg2J +HMflxc2gcSMdk7E5ByLz5f6QrFfSDFk02ZJTs4ssbbUEYohht9znPMQEaWVqATWE +3n0VspqZyoBNkH/agE5GiGZ/k/QyeqzMNj+c9kr43Upu8DpLrz8v2uAp5xNj3YVg +ihaeD6GW8+PQoEjZ3mrCmH7uGLmHxh7Am59LfEyNrDn+8Rq95WvkmbyHSVxZnBmo +h/6O3Jk+0/QhIXZ2hryMflPcYWeRGH0CAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU2eFK7+R3x/me8roIBNxBrplkM6EwDgYDVR0PAQH/BAQDAgGG +MA0GCSqGSIb3DQEBDAUAA4ICAQB5gWFe5s7ObQFj1fTO9L6gYgtFhnwdmxU0q8Ke +HWCrdFmyXdC39qdAFOwM5/7fa9zKmiMrZvy9HNvCXEp4Z7z9mHhBmuqPZQx0qPgU +uLdP8wGRuWryzp3g2oqkX9t31Z0JnkbIdp7kfRT6ME4I4VQsaY5Y3mh+hIHOUvcy +p+98i3UuEIcwJnVAV9wTTzrWusZl9iaQ1nSYbmkX9bBssJ2GmtW+T+VS/1hJ/Q4f +AlE3dOQkLFoPPb3YRWBHr2n1LPIqMVwDNAuWavRA2dSfaLl+kzbn/dua7HTQU5D4 +b2Fu2vLhGirwRJe+V7zdef+tI7sngXqjgObyOeG5O2BY3s+um6D4fS0Th3QchMO7 +0+GwcIgSgcjIjlrt6/xJwJLE8cRkUUieYKq1C4McpZWTF30WnzOPUzRzLHkcNzNA +0A7sKMK6QoYWo5Rmo8zewUxUqzc9oQSrYADP7PEwGncLtFe+dlRFx+PA1a+lcIgo +1ZGfXigYtQ3VKkcknyYlJ+hN4eCMBHtD81xDy9iP2MLE41JhLnoB2rVEtewO5diF +7o95Mwl84VMkLhhHPeGKSKzEbBtYYBifHNct+Bst8dru8UumTltgfX6urH3DN+/8 +JF+5h3U8oR2LL5y76cyeb+GWDXXy9zoQe2QvTyTy88LwZq1JzujYi2k8QiLLhFIf +FEv9Bg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICsDCCAjagAwIBAgIRAMgApnfGYPpK/fD0dbN2U4YwCgYIKoZIzj0EAwMwgZcx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwnQW1h +em9uIFJEUyBldS1zb3V0aC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMCAXDTIxMDUxOTE4MzgxMVoYDzIxMjEwNTE5MTkzODExWjCBlzELMAkG +A1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4xEzAR +BgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6b24g +UkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1NlYXR0 +bGUwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQfEWl6d4qSuIoECdZPp+39LaKsfsX7 +THs3/RrtT0+h/jl3bjZ7Qc68k16x+HGcHbaayHfqD0LPdzH/kKtNSfQKqemdxDQh +Z4pwkixJu8T1VpXZ5zzCvBXCl75UqgEFS92jQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFFPrSNtWS5JU+Tvi6ABV231XbjbEMA4GA1UdDwEB/wQEAwIBhjAK +BggqhkjOPQQDAwNoADBlAjEA+a7hF1IrNkBd2N/l7IQYAQw8chnRZDzh4wiGsZsC +6A83maaKFWUKIb3qZYXFSi02AjAbp3wxH3myAmF8WekDHhKcC2zDvyOiKLkg9Y6v +ZVmyMR043dscQbcsVoacOYv198c= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICtDCCAjqgAwIBAgIRAPhVkIsQ51JFhD2kjFK5uAkwCgYIKoZIzj0EAwMwgZkx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEyMDAGA1UEAwwpQW1h +em9uIFJEUyBldS1jZW50cmFsLTIgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjIwNjA2MjEyOTE3WhgPMjEyMjA2MDYyMjI5MTdaMIGZMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMjAwBgNVBAMMKUFtYXpv +biBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEA5xnIEBtG5b2nmbj49UEwQza +yX0844fXjccYzZ8xCDUe9dS2XOUi0aZlGblgSe/3lwjg8fMcKXLObGGQfgIx1+5h +AIBjORis/dlyN5q/yH4U5sjS8tcR0GDGVHrsRUZCo0IwQDAPBgNVHRMBAf8EBTAD +AQH/MB0GA1UdDgQWBBRK+lSGutXf4DkTjR3WNfv4+KeNFTAOBgNVHQ8BAf8EBAMC +AYYwCgYIKoZIzj0EAwMDaAAwZQIxAJ4NxQ1Gerqr70ZrnUqc62Vl8NNqTzInamCG +Kce3FTsMWbS9qkgrjZkO9QqOcGIw/gIwSLrwUT+PKr9+H9eHyGvpq9/3AIYSnFkb +Cf3dyWPiLKoAtLFwjzB/CkJlsAS1c8dS +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/jCCA+agAwIBAgIQGZH12Q7x41qIh9vDu9ikTjANBgkqhkiG9w0BAQwFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMyBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTI1MjIyMjMzWhgPMjEyMTA1MjUyMzIyMzNaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0zIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMqE47sHXWzdpuqj +JHb+6jM9tDbQLDFnYjDWpq4VpLPZhb7xPNh9gnYYTPKG4avG421EblAHqzy9D2pN +1z90yKbIfUb/Sy2MhQbmZomsObhONEra06fJ0Dydyjswf1iYRp2kwpx5AgkVoNo7 +3dlws73zFjD7ImKvUx2C7B75bhnw2pJWkFnGcswl8fZt9B5Yt95sFOKEz2MSJE91 +kZlHtya19OUxZ/cSGci4MlOySzqzbGwUqGxEIDlY8I39VMwXaYQ8uXUN4G780VcL +u46FeyRGxZGz2n3hMc805WAA1V5uir87vuirTvoSVREET97HVRGVVNJJ/FM6GXr1 +VKtptybbo81nefYJg9KBysxAa2Ao2x2ry/2ZxwhS6VZ6v1+90bpZA1BIYFEDXXn/ +dW07HSCFnYSlgPtSc+Muh15mdr94LspYeDqNIierK9i4tB6ep7llJAnq0BU91fM2 +JPeqyoTtc3m06QhLf68ccSxO4l8Hmq9kLSHO7UXgtdjfRVaffngopTNk8qK7bIb7 +LrgkqhiQw/PRCZjUdyXL153/fUcsj9nFNe25gM4vcFYwH6c5trd2tUl31NTi1MfG +Mgp3d2dqxQBIYANkEjtBDMy3SqQLIo9EymqmVP8xx2A/gCBgaxvMAsI6FSWRoC7+ +hqJ8XH4mFnXSHKtYMe6WPY+/XZgtAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8w +HQYDVR0OBBYEFIkXqTnllT/VJnI2NqipA4XV8rh1MA4GA1UdDwEB/wQEAwIBhjAN +BgkqhkiG9w0BAQwFAAOCAgEAKjSle8eenGeHgT8pltWCw/HzWyQruVKhfYIBfKJd +MhV4EnH5BK7LxBIvpXGsFUrb0ThzSw0fn0zoA9jBs3i/Sj6KyeZ9qUF6b8ycDXd+ +wHonmJiQ7nk7UuMefaYAfs06vosgl1rI7eBHC0itexIQmKh0aX+821l4GEgEoSMf +loMFTLXv2w36fPHHCsZ67ODldgcZbKNnpCTX0YrCwEYO3Pz/L398btiRcWGrewrK +jdxAAyietra8DRno1Zl87685tfqc6HsL9v8rVw58clAo9XAQvT+fmSOFw/PogRZ7 +OMHUat3gu/uQ1M5S64nkLLFsKu7jzudBuoNmcJysPlzIbqJ7vYc82OUGe9ucF3wi +3tbKQ983hdJiTExVRBLX/fYjPsGbG3JtPTv89eg2tjWHlPhCDMMxyRKl6isu2RTq +6VT489Z2zQrC33MYF8ZqO1NKjtyMAMIZwxVu4cGLkVsqFmEV2ScDHa5RadDyD3Ok +m+mqybhvEVm5tPgY6p0ILPMN3yvJsMSPSvuBXhO/X5ppNnpw9gnxpwbjQKNhkFaG +M5pkADZ14uRguOLM4VthSwUSEAr5VQYCFZhEwK+UOyJAGiB/nJz6IxL5XBNUXmRM +Hl8Xvz4riq48LMQbjcVQj0XvH941yPh+P8xOi00SGaQRaWp55Vyr4YKGbV0mEDz1 +r1o= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIF/zCCA+egAwIBAgIRAKwYju1QWxUZpn6D1gOtwgQwDQYJKoZIhvcNAQEMBQAw +gZcxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEwMC4GA1UEAwwn +QW1hem9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBSU0E0MDk2IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyMDE2NTM1NFoYDzIxMjEwNTIwMTc1MzU0WjCBlzEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdBbWF6 +b24gUkRTIGV1LXdlc3QtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCKdBP1U4lqWWkc +Cb25/BKRTsvNVnISiKocva8GAzJyKfcGRa85gmgu41U+Hz6+39K+XkRfM0YS4BvQ +F1XxWT0bNyypuvwCvmYShSTjN1TY0ltncDddahTajE/4MdSOZb/c98u0yt03cH+G +hVwRyT50h0v/UEol50VfwcVAEZEgcQQYhf1IFUFlIvKpmDOqLuFakOnc7c9akK+i +ivST+JO1tgowbnNkn2iLlSSgUWgb1gjaOsNfysagv1RXdlyPw3EyfwkFifAQvF2P +Q0ayYZfYS640cccv7efM1MSVyFHR9PrrDsF/zr2S2sGPbeHr7R/HwLl+S5J/l9N9 +y0rk6IHAWV4dEkOvgpnuJKURwA48iu1Hhi9e4moNS6eqoK2KmY3VFpuiyWcA73nH +GSmyaH+YuMrF7Fnuu7GEHZL/o6+F5cL3mj2SJJhL7sz0ryf5Cs5R4yN9BIEj/f49 +wh84pM6nexoI0Q4wiSFCxWiBpjSmOK6h7z6+2utaB5p20XDZHhxAlmlx4vMuWtjh +XckgRFxc+ZpVMU3cAHUpVEoO49e/+qKEpPzp8Xg4cToKw2+AfTk3cmyyXQfGwXMQ +ZUHNZ3w9ILMWihGCM2aGUsLcGDRennvNmnmin/SENsOQ8Ku0/a3teEzwV9cmmdYz +5iYs1YtgPvKFobY6+T2RXXh+A5kprwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/ +MB0GA1UdDgQWBBSyUrsQVnKmA8z6/2Ech0rCvqpNmTAOBgNVHQ8BAf8EBAMCAYYw +DQYJKoZIhvcNAQEMBQADggIBAFlj3IFmgiFz5lvTzFTRizhVofhTJsGr14Yfkuc7 +UrXPuXOwJomd4uot2d/VIeGJpfnuS84qGdmQyGewGTJ9inatHsGZgHl9NHNWRwKZ +lTKTbBiq7aqgtUSFa06v202wpzU+1kadxJJePrbABxiXVfOmIW/a1a4hPNcT3syH +FIEg1+CGsp71UNjBuwg3JTKWna0sLSKcxLOSOvX1fzxK5djzVpEsvQMB4PSAzXca +vENgg2ErTwgTA+4s6rRtiBF9pAusN1QVuBahYP3ftrY6f3ycS4K65GnqscyfvKt5 +YgjtEKO3ZeeX8NpubMbzC+0Z6tVKfPFk/9TXuJtwvVeqow0YMrLLyRiYvK7EzJ97 +rrkxoKnHYQSZ+rH2tZ5SE392/rfk1PJL0cdHnkpDkUDO+8cKsFjjYKAQSNC52sKX +74AVh6wMwxYwVZZJf2/2XxkjMWWhKNejsZhUkTISSmiLs+qPe3L67IM7GyKm9/m6 +R3r8x6NGjhTsKH64iYJg7AeKeax4b2e4hBb6GXFftyOs7unpEOIVkJJgM6gh3mwn +R7v4gwFbLKADKt1vHuerSZMiTuNTGhSfCeDM53XI/mjZl2HeuCKP1mCDLlaO+gZR +Q/G+E0sBKgEX4xTkAc3kgkuQGfExdGtnN2U2ehF80lBHB8+2y2E+xWWXih/ZyIcW +wOx+ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQM4C8g5iFRucSWdC8EdqHeDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMSBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjEwNTIxMjIyODI2WhgPMjEyMTA1MjEyMzI4MjZaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANeTsD/u +6saPiY4Sg0GlJlMXMBltnrcGAEkwq34OKQ0bCXqcoNJ2rcAMmuFC5x9Ho1Y3YzB7 +NO2GpIh6bZaO76GzSv4cnimcv9n/sQSYXsGbPD+bAtnN/RvNW1avt4C0q0/ghgF1 +VFS8JihIrgPYIArAmDtGNEdl5PUrdi9y6QGggbRfidMDdxlRdZBe1C18ZdgERSEv +UgSTPRlVczONG5qcQkUGCH83MMqL5MKQiby/Br5ZyPq6rxQMwRnQ7tROuElzyYzL +7d6kke+PNzG1mYy4cbYdjebwANCtZ2qYRSUHAQsOgybRcSoarv2xqcjO9cEsDiRU +l97ToadGYa4VVERuTaNZxQwrld4mvzpyKuirqZltOqg0eoy8VUsaRPL3dc5aChR0 +dSrBgRYmSAClcR2/2ZCWpXemikwgt031Dsc0A/+TmVurrsqszwbr0e5xqMow9LzO +MI/JtLd0VFtoOkL/7GG2tN8a+7gnLFxpv+AQ0DH5n4k/BY/IyS+H1erqSJhOTQ11 +vDOFTM5YplB9hWV9fp5PRs54ILlHTlZLpWGs3I2BrJwzRtg/rOlvsosqcge9ryai +AKm2j+JBg5wJ19R8oxRy8cfrNTftZePpISaLTyV2B16w/GsSjqixjTQe9LRN2DHk +cC+HPqYyzW2a3pUVyTGHhW6a7YsPBs9yzt6hAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFIqA8QkOs2cSirOpCuKuOh9VDfJfMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAOUI90mEIsa+vNJku0iUwdBMnHiO4gm7E +5JloP7JG0xUr7d0hypDorMM3zVDAL+aZRHsq8n934Cywj7qEp1304UF6538ByGdz +tkfacJsUSYfdlNJE9KbA4T+U+7SNhj9jvePpVjdQbhgzxITE9f8CxY/eM40yluJJ +PhbaWvOiRagzo74wttlcDerzLT6Y/JrVpWhnB7IY8HvzK+BwAdaCsBUPC3HF+kth +CIqLq7J3YArTToejWZAp5OOI6DLPM1MEudyoejL02w0jq0CChmZ5i55ElEMnapRX +7GQTARHmjgAOqa95FjbHEZzRPqZ72AtZAWKFcYFNk+grXSeWiDgPFOsq6mDg8DDB +0kfbYwKLFFCC9YFmYzR2YrWw2NxAScccUc2chOWAoSNHiqBbHR8ofrlJSWrtmKqd +YRCXzn8wqXnTS3NNHNccqJ6dN+iMr9NGnytw8zwwSchiev53Fpc1mGrJ7BKTWH0t +ZrA6m32wzpMymtKozlOPYoE5mtZEzrzHEXfa44Rns7XIHxVQSXVWyBHLtIsZOrvW +U5F41rQaFEpEeUQ7sQvqUoISfTUVRNDn6GK6YaccEhCji14APLFIvhRQUDyYMIiM +4vll0F/xgVRHTgDVQ8b8sxdhSYlqB4Wc2Ym41YRz+X2yPqk3typEZBpc4P5Tt1/N +89cEIGdbjsA= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQYjbPSg4+RNRD3zNxO1fuKDANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUyNDIwNTkyMVoYDzIwNjEwNTI0MjE1OTIxWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LW5vcnRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA179eQHxcV0YL +XMkqEmhSBazHhnRVd8yICbMq82PitE3BZcnv1Z5Zs/oOgNmMkOKae4tCXO/41JCX +wAgbs/eWWi+nnCfpQ/FqbLPg0h3dqzAgeszQyNl9IzTzX4Nd7JFRBVJXPIIKzlRf ++GmFsAhi3rYgDgO27pz3ciahVSN+CuACIRYnA0K0s9lhYdddmrW/SYeWyoB7jPa2 +LmWpAs7bDOgS4LlP2H3eFepBPgNufRytSQUVA8f58lsE5w25vNiUSnrdlvDrIU5n +Qwzc7NIZCx4qJpRbSKWrUtbyJriWfAkGU7i0IoainHLn0eHp9bWkwb9D+C/tMk1X +ERZw2PDGkwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBSFmR7s +dAblusFN+xhf1ae0KUqhWTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAHsXOpjPMyH9lDhPM61zYdja1ebcMVgfUvsDvt+w0xKMKPhBzYDMs/cFOi1N +Q8LV79VNNfI2NuvFmGygcvTIR+4h0pqqZ+wjWl3Kk5jVxCrbHg3RBX02QLumKd/i +kwGcEtTUvTssn3SM8bgM0/1BDXgImZPC567ciLvWDo0s/Fe9dJJC3E0G7d/4s09n +OMdextcxFuWBZrBm/KK3QF0ByA8MG3//VXaGO9OIeeOJCpWn1G1PjT1UklYhkg61 +EbsTiZVA2DLd1BGzfU4o4M5mo68l0msse/ndR1nEY6IywwpgIFue7+rEleDh6b9d +PYkG1rHVw2I0XDG4o17aOn5E94I= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQC6W4HFghUkkgyQw14a6JljANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIyMDUyMzE4MTYzMloYDzIwNjIwNTIzMTkxNjMyWjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTIgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAiM/t4FV2R9Nx +UQG203UY83jInTa/6TMq0SPyg617FqYZxvz2kkx09x3dmxepUg9ttGMlPgjsRZM5 +LCFEi1FWk+hxHzt7vAdhHES5tdjwds3aIkgNEillmRDVrUsbrDwufLaa+MMDO2E1 +wQ/JYFXw16WBCCi2g1EtyQ2Xp+tZDX5IWOTnvhZpW8vVDptZ2AcJ5rMhfOYO3OsK +5EF0GGA5ldzuezP+BkrBYGJ4wVKGxeaq9+5AT8iVZrypjwRkD7Y5CurywK3+aBwm +s9Q5Nd8t45JCOUzYp92rFKsCriD86n/JnEvgDfdP6Hvtm0/DkwXK40Wz2q0Zrd0k +mjP054NRPwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRR7yqd +SfKcX2Q8GzhcVucReIpewTAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAEszBRDwXcZyNm07VcFwI1Im94oKwKccuKYeJEsizTBsVon8VpEiMwDs+yGu +3p8kBhvkLwWybkD/vv6McH7T5b9jDX2DoOudqYnnaYeypsPH/00Vh3LvKagqzQza +orWLx+0tLo8xW4BtU+Wrn3JId8LvAhxyYXTn9bm+EwPcStp8xGLwu53OPD1RXYuy +uu+3ps/2piP7GVfou7H6PRaqbFHNfiGg6Y+WA0HGHiJzn8uLmrRJ5YRdIOOG9/xi +qTmAZloUNM7VNuurcMM2hWF494tQpsQ6ysg2qPjbBqzlGoOt3GfBTOZmqmwmqtam +K7juWM/mdMQAJ3SMlE5wI8nVdx4= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIICrjCCAjSgAwIBAgIRAL9SdzVPcpq7GOpvdGoM80IwCgYIKoZIzj0EAwMwgZYx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTEvMC0GA1UEAwwmQW1h +em9uIFJEUyBldS13ZXN0LTEgUm9vdCBDQSBFQ0MzODQgRzExEDAOBgNVBAcMB1Nl +YXR0bGUwIBcNMjEwNTIwMTY1ODA3WhgPMjEyMTA1MjAxNzU4MDdaMIGWMQswCQYD +VQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjETMBEG +A1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExLzAtBgNVBAMMJkFtYXpvbiBS +RFMgZXUtd2VzdC0xIFJvb3QgQ0EgRUNDMzg0IEcxMRAwDgYDVQQHDAdTZWF0dGxl +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEJWDgXebvwjR+Ce+hxKOLbnsfN5W5dOlP +Zn8kwWnD+SLkU81Eac/BDJsXGrMk6jFD1vg16PEkoSevsuYWlC8xR6FmT6F6pmeh +fsMGOyJpfK4fyoEPhKeQoT23lFIc5Orjo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0G +A1UdDgQWBBSVNAN1CHAz0eZ77qz2adeqjm31TzAOBgNVHQ8BAf8EBAMCAYYwCgYI +KoZIzj0EAwMDaAAwZQIxAMlQeHbcjor49jqmcJ9gRLWdEWpXG8thIf6zfYQ/OEAg +d7GDh4fR/OUk0VfjsBUN/gIwZB0bGdXvK38s6AAE/9IT051cz/wMe9GIrX1MnL1T +1F5OqnXJdiwfZRRTHsRQ/L00 +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGBDCCA+ygAwIBAgIQalr16vDfX4Rsr+gfQ4iVFDANBgkqhkiG9w0BAQwFADCB +mjELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTMwMQYDVQQDDCpB +bWF6b24gUkRTIGV1LWNlbnRyYWwtMiBSb290IENBIFJTQTQwOTYgRzExEDAOBgNV +BAcMB1NlYXR0bGUwIBcNMjIwNjA2MjEyNTIzWhgPMjEyMjA2MDYyMjI1MjNaMIGa +MQswCQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5j +LjETMBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMzAxBgNVBAMMKkFt +YXpvbiBSRFMgZXUtY2VudHJhbC0yIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANbHbFg7 +2VhZor1YNtez0VlNFaobS3PwOMcEn45BE3y7HONnElIIWXGQa0811M8V2FnyqnE8 +Z5aO1EuvijvWf/3D8DPZkdmAkIfh5hlZYY6Aatr65kEOckwIAm7ZZzrwFogYuaFC +z/q0CW+8gxNK+98H/zeFx+IxiVoPPPX6UlrLvn+R6XYNERyHMLNgoZbbS5gGHk43 +KhENVv3AWCCcCc85O4rVd+DGb2vMVt6IzXdTQt6Kih28+RGph+WDwYmf+3txTYr8 +xMcCBt1+whyCPlMbC+Yn/ivtCO4LRf0MPZDRQrqTTrFf0h/V0BGEUmMGwuKgmzf5 +Kl9ILdWv6S956ioZin2WgAxhcn7+z//sN++zkqLreSf90Vgv+A7xPRqIpTdJ/nWG +JaAOUofBfsDsk4X4SUFE7xJa1FZAiu2lqB/E+y7jnWOvFRalzxVJ2Y+D/ZfUfrnK +4pfKtyD1C6ni1celrZrAwLrJ3PoXPSg4aJKh8+CHex477SRsGj8KP19FG8r0P5AG +8lS1V+enFCNvT5KqEBpDZ/Y5SQAhAYFUX+zH4/n4ql0l/emS+x23kSRrF+yMkB9q +lhC/fMk6Pi3tICBjrDQ8XAxv56hfud9w6+/ljYB2uQ1iUYtlE3JdIiuE+3ws26O8 +i7PLMD9zQmo+sVi12pLHfBHQ6RRHtdVRXbXRAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wHQYDVR0OBBYEFBFot08ipEL9ZUXCG4lagmF53C0/MA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQwFAAOCAgEAi2mcZi6cpaeqJ10xzMY0F3L2eOKYnlEQ +h6QyhmNKCUF05q5u+cok5KtznzqMwy7TFOZtbVHl8uUX+xvgq/MQCxqFAnuStBXm +gr2dg1h509ZwvTdk7TDxGdftvPCfnPNJBFbMSq4CZtNcOFBg9Rj8c3Yj+Qvwd56V +zWs65BUkDNJrXmxdvhJZjUkMa9vi/oFN+M84xXeZTaC5YDYNZZeW9706QqDbAVES +5ulvKLavB8waLI/lhRBK5/k0YykCMl0A8Togt8D1QsQ0eWWbIM8/HYJMPVFhJ8Wj +vT1p/YVeDA3Bo1iKDOttgC5vILf5Rw1ZEeDxjf/r8A7VS13D3OLjBmc31zxRTs3n +XvHKP9MieQHn9GE44tEYPjK3/yC6BDFzCBlvccYHmqGb+jvDEXEBXKzimdC9mcDl +f4BBQWGJBH5jkbU9p6iti19L/zHhz7qU6UJWbxY40w92L9jS9Utljh4A0LCTjlnR +NQUgjnGC6K+jkw8hj0LTC5Ip87oqoT9w7Av5EJ3VJ4hcnmNMXJJ1DkWYdnytcGpO +DMVITQzzDZRwhbitCVPHagTN2wdi9TEuYE33J0VmFeTc6FSI50wP2aOAZ0Q1/8Aj +bxeM5jS25eaHc2CQAuhrc/7GLnxOcPwdWQb2XWT8eHudhMnoRikVv/KSK3mf6om4 +1YfpdH2jp30= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIID/jCCAuagAwIBAgIQTDc+UgTRtYO7ZGTQ8UWKDDANBgkqhkiG9w0BAQsFADCB +lzELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTAwLgYDVQQDDCdB +bWF6b24gUkRTIGV1LXdlc3QtMiBSb290IENBIFJTQTIwNDggRzExEDAOBgNVBAcM +B1NlYXR0bGUwIBcNMjEwNTIxMjI0NjI0WhgPMjA2MTA1MjEyMzQ2MjRaMIGXMQsw +CQYDVQQGEwJVUzEiMCAGA1UECgwZQW1hem9uIFdlYiBTZXJ2aWNlcywgSW5jLjET +MBEGA1UECwwKQW1hem9uIFJEUzELMAkGA1UECAwCV0ExMDAuBgNVBAMMJ0FtYXpv +biBSRFMgZXUtd2VzdC0yIFJvb3QgQ0EgUlNBMjA0OCBHMTEQMA4GA1UEBwwHU2Vh +dHRsZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM1oGtthQ1YiVIC2 +i4u4swMAGxAjc/BZp0yq0eP5ZQFaxnxs7zFAPabEWsrjeDzrRhdVO0h7zskrertP +gblGhfD20JfjvCHdP1RUhy/nzG+T+hn6Takan/GIgs8grlBMRHMgBYHW7tklhjaH +3F7LujhceAHhhgp6IOrpb6YTaTTaJbF3GTmkqxSJ3l1LtEoWz8Al/nL/Ftzxrtez +Vs6ebpvd7sw37sxmXBWX2OlvUrPCTmladw9OrllGXtCFw4YyLe3zozBlZ3cHzQ0q +lINhpRcajTMfZrsiGCkQtoJT+AqVJPS2sHjqsEH8yiySW9Jbq4zyMbM1yqQ2vnnx +MJgoYMcCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUaQG88UnV +JPTI+Pcti1P+q3H7pGYwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBCwUAA4IB +AQBAkgr75V0sEJimC6QRiTVWEuj2Khy7unjSfudbM6zumhXEU2/sUaVLiYy6cA/x +3v0laDle6T07x9g64j5YastE/4jbzrGgIINFlY0JnaYmR3KZEjgi1s1fkRRf3llL +PJm9u4Q1mbwAMQK/ZjLuuRcL3uRIHJek18nRqT5h43GB26qXyvJqeYYpYfIjL9+/ +YiZAbSRRZG+Li23cmPWrbA1CJY121SB+WybCbysbOXzhD3Sl2KSZRwSw4p2HrFtV +1Prk0dOBtZxCG9luf87ultuDZpfS0w6oNBAMXocgswk24ylcADkkFxBWW+7BETn1 +EpK+t1Lm37mU4sxtuha00XAi +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEADCCAuigAwIBAgIQcY44/8NUvBwr6LlHfRy7KjANBgkqhkiG9w0BAQsFADCB +mDELMAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIElu +Yy4xEzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChB +bWF6b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQH +DAdTZWF0dGxlMCAXDTIxMDUxOTE4MjcxOFoYDzIwNjEwNTE5MTkyNzE4WjCBmDEL +MAkGA1UEBhMCVVMxIjAgBgNVBAoMGUFtYXpvbiBXZWIgU2VydmljZXMsIEluYy4x +EzARBgNVBAsMCkFtYXpvbiBSRFMxCzAJBgNVBAgMAldBMTEwLwYDVQQDDChBbWF6 +b24gUkRTIGV1LXNvdXRoLTEgUm9vdCBDQSBSU0EyMDQ4IEcxMRAwDgYDVQQHDAdT +ZWF0dGxlMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0UaBeC+Usalu +EtXnV7+PnH+gi7/71tI/jkKVGKuhD2JDVvqLVoqbMHRh3+wGMvqKCjbHPcC2XMWv +566fpAj4UZ9CLB5fVzss+QVNTl+FH2XhEzigopp+872ajsNzcZxrMkifxGb4i0U+ +t0Zi+UrbL5tsfP2JonKR1crOrbS6/DlzHBjIiJazGOQcMsJjNuTOItLbMohLpraA +/nApa3kOvI7Ufool1/34MG0+wL3UUA4YkZ6oBJVxjZvvs6tI7Lzz/SnhK2widGdc +snbLqBpHNIZQSorVoiwcFaRBGYX/uzYkiw44Yfa4cK2V/B5zgu1Fbr0gbI2am4eh +yVYyg4jPawIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBS9gM1m +IIjyh9O5H/7Vj0R/akI7UzAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZIhvcNAQELBQAD +ggEBAF0Sm9HC2AUyedBVnwgkVXMibnYChOzz7T+0Y+fOLXYAEXex2s8oqGeZdGYX +JHkjBn7JXu7LM+TpTbPbFFDoc1sgMguD/ls+8XsqAl1CssW+amryIL+jfcfbgQ+P +ICwEUD9hGdjBgJ5WcuS+qqxHsEIlFNci3HxcxfBa9VsWs5TjI7Vsl4meL5lf7ZyL +wDV7dHRuU+cImqG1MIvPRIlvPnT7EghrCYi2VCPhP2pM/UvShuwVnkz4MJ29ebIk +WR9kpblFxFdE92D5UUvMCjC2kmtgzNiErvTcwIvOO9YCbBHzRB1fFiWrXUHhJWq9 +IkaxR5icb/IpAV0A1lYZEWMVsfQ= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGATCCA+mgAwIBAgIRAMa0TPL+QgbWfUPpYXQkf8wwDQYJKoZIhvcNAQEMBQAw +gZgxCzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJ +bmMuMRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwo +QW1hem9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UE +BwwHU2VhdHRsZTAgFw0yMTA1MjQyMTAzMjBaGA8yMTIxMDUyNDIyMDMyMFowgZgx +CzAJBgNVBAYTAlVTMSIwIAYDVQQKDBlBbWF6b24gV2ViIFNlcnZpY2VzLCBJbmMu +MRMwEQYDVQQLDApBbWF6b24gUkRTMQswCQYDVQQIDAJXQTExMC8GA1UEAwwoQW1h +em9uIFJEUyBldS1ub3J0aC0xIFJvb3QgQ0EgUlNBNDA5NiBHMTEQMA4GA1UEBwwH +U2VhdHRsZTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANhS9LJVJyWp +6Rudy9t47y6kzvgnFYDrvJVtgEK0vFn5ifdlHE7xqMz4LZqWBFTnS+3oidwVRqo7 +tqsuuElsouStO8m315/YUzKZEPmkw8h5ufWt/lg3NTCoUZNkB4p4skr7TspyMUwE +VdlKQuWTCOLtofwmWT+BnFF3To6xTh3XPlT3ssancw27Gob8kJegD7E0TSMVsecP +B8je65+3b8CGwcD3QB3kCTGLy87tXuS2+07pncHvjMRMBdDQQQqhXWsRSeUNg0IP +xdHTWcuwMldYPWK5zus9M4dCNBDlmZjKdcZZVUOKeBBAm7Uo7CbJCk8r/Fvfr6mw +nXXDtuWhqn/WhJiI/y0QU27M+Hy5CQMxBwFsfAjJkByBpdXmyYxUgTmMpLf43p7H +oWfH1xN0cT0OQEVmAQjMakauow4AQLNkilV+X6uAAu3STQVFRSrpvMen9Xx3EPC3 +G9flHueTa71bU65Xe8ZmEmFhGeFYHY0GrNPAFhq9RThPRY0IPyCZe0Th8uGejkek +jQjm0FHPOqs5jc8CD8eJs4jSEFt9lasFLVDcAhx0FkacLKQjGHvKAnnbRwhN/dF3 +xt4oL8Z4JGPCLau056gKnYaEyviN7PgO+IFIVOVIdKEBu2ASGE8/+QJB5bcHefNj +04hEkDW0UYJbSfPpVbGAR0gFI/QpycKnAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMB +Af8wHQYDVR0OBBYEFFMXvvjoaGGUcul8GA3FT05DLbZcMA4GA1UdDwEB/wQEAwIB +hjANBgkqhkiG9w0BAQwFAAOCAgEAQLwFhd2JKn4K/6salLyIA4mP58qbA/9BTB/r +D9l0bEwDlVPSdY7R3gZCe6v7SWLfA9RjE5tdWDrQMi5IU6W2OVrVsZS/yGJfwnwe +a/9iUAYprA5QYKDg37h12XhVsDKlYCekHdC+qa5WwB1SL3YUprDLPWeaIQdg+Uh2 ++LxvpZGoxoEbca0fc7flwq9ke/3sXt/3V4wJDyY6AL2YNdjFzC+FtYjHHx8rYxHs +aesP7yunuN17KcfOZBBnSFRrx96k+Xm95VReTEEpwiBqAECqEpMbd+R0mFAayMb1 +cE77GaK5yeC2f67NLYGpkpIoPbO9p9rzoXLE5GpSizMjimnz6QCbXPFAFBDfSzim +u6azp40kEUO6kWd7rBhqRwLc43D3TtNWQYxMve5mTRG4Od+eMKwYZmQz89BQCeqm +aZiJP9y9uwJw4p/A5V3lYHTDQqzmbOyhGUk6OdpdE8HXs/1ep1xTT20QDYOx3Ekt +r4mmNYfH/8v9nHNRlYJOqFhmoh1i85IUl5IHhg6OT5ZTTwsGTSxvgQQXrmmHVrgZ +rZIqyBKllCgVeB9sMEsntn4bGLig7CS/N1y2mYdW/745yCLZv2gj0NXhPqgEIdVV +f9DhFD4ohE1C63XP0kOQee+LYg/MY5vH8swpCSWxQgX5icv5jVDz8YTdCKgUc5u8 +rM2p0kk= +-----END CERTIFICATE----- diff --git a/examples/lambda-rds-iam-auth/src/main.rs b/examples/lambda-rds-iam-auth/src/main.rs new file mode 100644 index 00000000..32cf3580 --- /dev/null +++ b/examples/lambda-rds-iam-auth/src/main.rs @@ -0,0 +1,109 @@ +use aws_config::BehaviorVersion; +use aws_credential_types::provider::ProvideCredentials; +use aws_sigv4::{ + http_request::{sign, SignableBody, SignableRequest, SigningSettings}, + sign::v4, +}; +use lambda_runtime::{run, service_fn, Error, LambdaEvent}; +use serde_json::{json, Value}; +use sqlx::postgres::PgConnectOptions; +use std::env; +use std::time::{Duration, SystemTime}; + +const RDS_CERTS: &[u8] = include_bytes!("global-bundle.pem"); + +async fn generate_rds_iam_token( + db_hostname: &str, + port: u16, + db_username: &str, +) -> Result { + let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await; + + let credentials = config + .credentials_provider() + .expect("no credentials provider found") + .provide_credentials() + .await + .expect("unable to load credentials"); + let identity = credentials.into(); + let region = config.region().unwrap().to_string(); + + let mut signing_settings = SigningSettings::default(); + signing_settings.expires_in = Some(Duration::from_secs(900)); + signing_settings.signature_location = aws_sigv4::http_request::SignatureLocation::QueryParams; + + let signing_params = v4::SigningParams::builder() + .identity(&identity) + .region(®ion) + .name("rds-db") + .time(SystemTime::now()) + .settings(signing_settings) + .build()?; + + let url = format!( + "https://{db_hostname}:{port}/?Action=connect&DBUser={db_user}", + db_hostname = db_hostname, + port = port, + db_user = db_username + ); + + let signable_request = + SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])) + .expect("signable request"); + + let (signing_instructions, _signature) = + sign(signable_request, &signing_params.into())?.into_parts(); + + let mut url = url::Url::parse(&url).unwrap(); + for (name, value) in signing_instructions.params() { + url.query_pairs_mut().append_pair(name, &value); + } + + let response = url.to_string().split_off("https://".len()); + + Ok(response) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(handler)).await +} + +async fn handler(_event: LambdaEvent) -> Result { + let db_host = env::var("DB_HOSTNAME").expect("DB_HOSTNAME must be set"); + let db_port = env::var("DB_PORT") + .expect("DB_PORT must be set") + .parse::() + .expect("PORT must be a valid number"); + let db_name = env::var("DB_NAME").expect("DB_NAME must be set"); + let db_user_name = env::var("DB_USERNAME").expect("DB_USERNAME must be set"); + + let token = generate_rds_iam_token(&db_host, db_port, &db_user_name).await?; + + let opts = PgConnectOptions::new() + .host(&db_host) + .port(db_port) + .username(&db_user_name) + .password(&token) + .database(&db_name) + .ssl_root_cert_from_pem(RDS_CERTS.to_vec()) + .ssl_mode(sqlx::postgres::PgSslMode::Require); + + let pool = sqlx::postgres::PgPoolOptions::new() + .connect_with(opts) + .await?; + + let result: i32 = sqlx::query_scalar("SELECT $1 + $2") + .bind(3) + .bind(2) + .fetch_one(&pool) + .await?; + + println!("Result: {:?}", result); + + Ok(json!({ + "statusCode": 200, + "content-type": "text/plain", + "body": format!("The selected sum is: {result}") + })) +} From 309e3abb4f266b502da67ac8e35ea8a6b531090d Mon Sep 17 00:00:00 2001 From: Martin Jesper Low Madsen Date: Thu, 11 Jul 2024 00:57:24 +0200 Subject: [PATCH 145/211] Expand on the CloudTrail event (#909) * Expand on the CloudTrail event The initial contribution of the CloudTrail event was not complete, but rather constructed from a handful of event examples. The structure is documented at https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html which covers a number of optional fields and fields that are not already covered. * Add three example tests for CloudTrail events --- .../src/event/cloudwatch_events/cloudtrail.rs | 77 +++++++++++++++++-- ...le-cloudwatch-cloudtrail-assumed-role.json | 52 +++++++++++++ ...loudwatch-cloudtrail-unknown-federate.json | 27 +++++++ ...oudwatch-cloudtrail-unknown-user-auth.json | 33 ++++++++ 4 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 36d071ea..3f6bcc3e 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -1,3 +1,4 @@ +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -26,23 +27,89 @@ pub struct AWSAPICall { #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct UserIdentity { +pub struct SessionIssuer { pub r#type: String, + pub user_name: Option, pub principal_id: String, pub arn: String, pub account_id: String, - pub session_context: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] -pub struct SessionContext { - pub attributes: Attributes, +pub struct WebIdFederationData { + pub federated_provider: Option, + pub attributes: Option, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attributes { pub mfa_authenticated: String, - pub creation_date: String, + pub creation_date: DateTime, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContext { + pub session_issuer: Option, + pub web_id_federation_data: Option, + pub attributes: Attributes, + pub source_identity: Option, + pub ec2_role_delivery: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OnBehalfOf { + pub user_id: String, + pub identity_store_arn: String, +} + +// https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + pub account_id: Option, + pub arn: Option, + pub credential_id: Option, + pub invoked_by: Option, + pub principal_id: Option, + pub session_context: Option, + pub user_name: Option, + pub on_behalf_of: Option, +} + +#[cfg(test)] +mod tests { + use super::AWSAPICall; + + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_unknown_assumed_role() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-assumed-role.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_unknown_federate() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-federate.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + #[test] + #[cfg(feature = "cloudwatch_events")] + fn example_cloudwatch_cloudtrail_assumed_role() { + let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json"); + let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json new file mode 100644 index 00000000..6e8946e9 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-assumed-role.json @@ -0,0 +1,52 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "AssumedRole", + "principalId": "ZZZZZZZZZZZZZZZZZZZZZ:me@dev.com", + "arn": "arn:aws:sts::123456789000:assumed-role/AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890/me@dev.com", + "accountId": "123456789000", + "accessKeyId": "ABCDEFGHI12345678890", + "sessionContext": { + "sessionIssuer": { + "type": "Role", + "principalId": "ZZZZZZZZZZZZZZZZZZZZZ", + "arn": "arn:aws:iam::123456789000:role/aws-reserved/sso.amazonaws.com/eu-west-1/AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890", + "accountId": "123456789000", + "userName": "AWSReservedSSO_AWSAdministratorAccess_abcdef1234567890" + }, + "webIdFederationData": {}, + "attributes": { + "creationDate": "2024-07-10T16:03:25Z", + "mfaAuthenticated": "false" + } + }, + "invokedBy": "servicecatalog.amazonaws.com" + }, + "eventTime": "2024-07-10T16:48:26Z", + "eventSource": "controltower.amazonaws.com", + "eventName": "CreateManagedAccount", + "awsRegion": "eu-west-1", + "sourceIPAddress": "servicecatalog.amazonaws.com", + "userAgent": "servicecatalog.amazonaws.com", + "requestParameters": { + "accountEmail": "HIDDEN_DUE_TO_SECURITY_REASONS", + "accountName": "Account Name", + "parentOrganizationalUnitName": "Organizational Unit (ou-a1b2-abcdef12)", + "sSOUserEmail": "HIDDEN_DUE_TO_SECURITY_REASONS", + "sSOUserFirstName": "HIDDEN_DUE_TO_SECURITY_REASONS", + "sSOUserLastName": "HIDDEN_DUE_TO_SECURITY_REASONS", + "provisionedProductId": "pp-abcdefg123456", + "idempotencyToken": "abcdef12345-abcdef12345" + }, + "responseElements": { + "createManagedAccountExecutionId": "123456789000-abcdef12345-abcdef12345" + }, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsApiCall", + "managementEvent": true, + "recipientAccountId": "123456789000", + "eventCategory": "Management", + "sessionCredentialFromConsole": "true" +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json new file mode 100644 index 00000000..336edc1f --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-federate.json @@ -0,0 +1,27 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "Unknown", + "principalId": "00000000-0000-0000-0000-000000000000", + "accountId": "123456789000", + "userName": "me@dev.com" + }, + "eventTime": "2024-07-10T18:41:56Z", + "eventSource": "sso.amazonaws.com", + "eventName": "Federate", + "awsRegion": "eu-west-1", + "sourceIPAddress": "1.1.1.1", + "userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "requestParameters": null, + "responseElements": null, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsServiceEvent", + "managementEvent": true, + "recipientAccountId": "123456789000", + "serviceEventDetails": { + "relayId": "00000000-0000-0000-0000-000000000000_00000000-0000-0000-0000-000000000000" + }, + "eventCategory": "Management" +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json new file mode 100644 index 00000000..96fed176 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json @@ -0,0 +1,33 @@ +{ + "eventVersion": "1.08", + "userIdentity": { + "type": "Unknown", + "principalId": "123456789000", + "arn": "", + "accountId": "123456789000", + "accessKeyId": "" + }, + "eventTime": "2024-07-10T16:05:11Z", + "eventSource": "signin.amazonaws.com", + "eventName": "UserAuthentication", + "awsRegion": "eu-west-1", + "sourceIPAddress": "1.1.1.1", + "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36", + "requestParameters": null, + "responseElements": null, + "additionalEventData": { + "AuthWorkflowID": "00000000-0000-0000-0000-000000000000", + "LoginTo": "https://tenant.awsapps.com/start/", + "CredentialType": "EXTERNAL_IDP" + }, + "requestID": "00000000-0000-0000-0000-000000000000", + "eventID": "00000000-0000-0000-0000-000000000000", + "readOnly": false, + "eventType": "AwsServiceEvent", + "managementEvent": true, + "recipientAccountId": "123456789000", + "serviceEventDetails": { + "UserAuthentication": "Success" + }, + "eventCategory": "Management" +} From e8761dae829a09c0858b2702dcef7d3853c3d377 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 12 Jul 2024 12:58:22 -0700 Subject: [PATCH 146/211] Error handling improvements (#907) * Error handling improvements Change how we handle error types to be more ergonomic: - Replace Cow with String: Diagnostics are serialized into JSON as soon as the function returns, which means that their value is copied right away. The performance improvement of using Cow is minimal in this case, but it has some ergonomic implications because we have to handle their lifetimes. By removing the explicit lifetimes, people can return Diagnostic values with static lifetimes which was not possible before. - Add `IntoDiagnostic` trait. This is a helper trait to facilitate transforming value types into Diagnostic. It gives external crates a better mechanism to transform values into `Diagnostic`. - Add features to implement `IntoDiagnostic` for anyhow, eyre, and miette error types. This helps people that use those creates to transform their errors into `Diagnostic` without double boxing their errors. * Recommend to implement `From`. Signed-off-by: David Calavera * Simplify implementation, remove `IntoDiagnostic` We can implement `From for Diagnostic` for error crates. * Remove implementation for From> Signed-off-by: David Calavera * Document features in Cargo.toml Signed-off-by: David Calavera --------- Signed-off-by: David Calavera --- README.md | 57 +++++- examples/basic-error-anyhow/Cargo.toml | 10 -- examples/basic-error-anyhow/src/main.rs | 21 --- .../.gitignore | 0 .../Cargo.toml | 12 ++ .../README.md | 6 +- .../src/main.rs | 44 +++++ .../.gitignore | 0 .../Cargo.toml | 2 +- .../README.md | 0 .../src/main.rs | 4 +- lambda-http/Cargo.toml | 6 +- lambda-http/src/lib.rs | 2 +- lambda-http/src/streaming.rs | 2 +- lambda-runtime/Cargo.toml | 10 +- lambda-runtime/src/diagnostic.rs | 162 ++++++++++++------ lambda-runtime/src/layers/api_response.rs | 21 ++- lambda-runtime/src/layers/panic.rs | 27 ++- lambda-runtime/src/lib.rs | 2 +- lambda-runtime/src/requests.rs | 8 +- lambda-runtime/src/runtime.rs | 10 +- 21 files changed, 275 insertions(+), 131 deletions(-) delete mode 100644 examples/basic-error-anyhow/Cargo.toml delete mode 100644 examples/basic-error-anyhow/src/main.rs rename examples/{basic-error-anyhow => basic-error-error-crates-integration}/.gitignore (100%) create mode 100644 examples/basic-error-error-crates-integration/Cargo.toml rename examples/{basic-error-anyhow => basic-error-error-crates-integration}/README.md (54%) create mode 100644 examples/basic-error-error-crates-integration/src/main.rs rename examples/{basic-error-diagnostic => basic-error-thiserror}/.gitignore (100%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/Cargo.toml (94%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/README.md (100%) rename examples/{basic-error-diagnostic => basic-error-thiserror}/src/main.rs (90%) diff --git a/README.md b/README.md index 44ec1983..31ba399b 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ pip3 install cargo-lambda See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). -### Your first function +## Your first function To create your first function, run Cargo Lambda with the [subcommand `new`](https://www.cargo-lambda.info/commands/new.html). This command will generate a Rust package with the initial source code for your function: @@ -71,6 +71,61 @@ async fn func(event: LambdaEvent) -> Result { } ``` +## Understanding Lambda errors + +when a function invocation fails, AWS Lambda expects you to return an object that can be serialized into JSON structure with the error information. This structure is represented in the following example: + +```json +{ + "error_type": "the type of error raised", + "error_message": "a string description of the error" +} +``` + +The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the converstion of several general errors types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. + +### Implement your own Diagnostic + +To get more descriptive `error_type` fields, you can implement `From` for your error type. That gives you full control on what the `error_type` is: + +```rust +use lambda_runtime::{Diagnostic, Error, LambdaEvent}; + +#[derive(Debug)] +struct ErrorResponse(&'static str); + +impl From for Diagnostic { + fn from(error: ErrorResponse) -> Diagnostic { + Diagnostic { + error_type: "MyErrorType".into(), + error_message: error.0.to_string(), + } + } +} + +async fn handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { + Err(ErrorResponse("this is an error response")) +} +``` + +We recommend you to use the [thiserror crate](https://crates.io/crates/thiserror) to declare your errors. You can see an example on how to integrate `thiserror` with the Runtime's diagnostics in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-thiserror) + +### Anyhow, Eyre, and Miette + +Popular error crates like Anyhow, Eyre, and Miette provide their own error types that encapsulate other errors. There is no direct transformation of those errors into `Diagnostic`, but we provide feature flags for each one of those crates to help you integrate them with your Lambda functions. + +If you enable the features `anyhow`, `eyre`, or `miette` in the `lambda_runtime` dependency of your package. The error types provided by those crates can have blanket transformations into `Diagnostic`. These features expose an `From for Diagnostic` implementation that transforms those error types into a `Diagnostic`. This is an example that transforms an `anyhow::Error` into a `Diagnostic`: + +```rust +use lambda_runtime::{Diagnostic, LambdaEvent}; + +async fn handler(_event: LambdaEvent) -> Result<(), Diagnostic> { + Err(anyhow::anyhow!("this is an error").into()) +} +``` + +You can see more examples on how to use these error crates in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-error-crates-integration). + ## Building and deploying your Lambda functions If you already have Cargo Lambda installed in your machine, run the next command to build your function: diff --git a/examples/basic-error-anyhow/Cargo.toml b/examples/basic-error-anyhow/Cargo.toml deleted file mode 100644 index a0ff62db..00000000 --- a/examples/basic-error-anyhow/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "basic-error-anyhow" -version = "0.1.0" -edition = "2021" - -[dependencies] -anyhow = "1" -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1" -tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/src/main.rs b/examples/basic-error-anyhow/src/main.rs deleted file mode 100644 index cbca84fd..00000000 --- a/examples/basic-error-anyhow/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -use anyhow::bail; -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::Deserialize; - -#[derive(Deserialize)] -struct Request {} - -/// Return anyhow::Result in the main body for the Lambda function. -async fn function_handler(_event: LambdaEvent) -> anyhow::Result<()> { - bail!("This is an error message"); -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - lambda_runtime::run(service_fn(|event: LambdaEvent| async move { - function_handler(event) - .await - .map_err(Into::>::into) - })) - .await -} diff --git a/examples/basic-error-anyhow/.gitignore b/examples/basic-error-error-crates-integration/.gitignore similarity index 100% rename from examples/basic-error-anyhow/.gitignore rename to examples/basic-error-error-crates-integration/.gitignore diff --git a/examples/basic-error-error-crates-integration/Cargo.toml b/examples/basic-error-error-crates-integration/Cargo.toml new file mode 100644 index 00000000..741ec713 --- /dev/null +++ b/examples/basic-error-error-crates-integration/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "basic-error-error-crates-integration" +version = "0.1.0" +edition = "2021" + +[dependencies] +anyhow = "1" +eyre = "0.6.12" +lambda_runtime = { path = "../../lambda-runtime", features = ["anyhow", "eyre", "miette"] } +miette = "7.2.0" +serde = "1" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-anyhow/README.md b/examples/basic-error-error-crates-integration/README.md similarity index 54% rename from examples/basic-error-anyhow/README.md rename to examples/basic-error-error-crates-integration/README.md index b659c283..2e46c55d 100644 --- a/examples/basic-error-anyhow/README.md +++ b/examples/basic-error-error-crates-integration/README.md @@ -1,6 +1,8 @@ -# AWS Lambda Function Error Handling With `anyhow` Crate Example +# AWS Lambda Function Error Handling with several popular error crates. -This example shows how to use external error types like `anyhow::Error`. +This example shows how to use external error types like `anyhow::Error`, `eyre::Report`, and `miette::Report`. + +To use the integrations with these crates, you need to enable to respective feature flag in the runtime which provides the implemetation of `into_diagnostic` for specific error types provided by these crates. ## Build & Deploy diff --git a/examples/basic-error-error-crates-integration/src/main.rs b/examples/basic-error-error-crates-integration/src/main.rs new file mode 100644 index 00000000..f4048584 --- /dev/null +++ b/examples/basic-error-error-crates-integration/src/main.rs @@ -0,0 +1,44 @@ +use lambda_runtime::{run, service_fn, Diagnostic, Error, LambdaEvent}; +use serde::Deserialize; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +enum ErrorType { + Anyhow, + Eyre, + Miette, +} + +#[derive(Deserialize)] +struct Request { + error_type: ErrorType, +} + +fn anyhow_error() -> anyhow::Result<()> { + anyhow::bail!("This is an error message from Anyhow"); +} + +fn eyre_error() -> eyre::Result<()> { + eyre::bail!("This is an error message from Eyre"); +} + +fn miette_error() -> miette::Result<()> { + miette::bail!("This is an error message from Miette"); +} + +/// Transform an anyhow::Error, eyre::Report, or miette::Report into a lambda_runtime::Diagnostic. +/// It does it by enabling the feature `anyhow`, `eyre` or `miette` in the runtime dependency. +/// Those features enable the implementation of `From for Diagnostic` +/// for `anyhow::Error`, `eyre::Report`, and `miette::Report`. +async fn function_handler(event: LambdaEvent) -> Result<(), Diagnostic> { + match event.payload.error_type { + ErrorType::Anyhow => anyhow_error().map_err(|e| e.into()), + ErrorType::Eyre => eyre_error().map_err(|e| e.into()), + ErrorType::Miette => miette_error().map_err(|e| e.into()), + } +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + run(service_fn(function_handler)).await +} diff --git a/examples/basic-error-diagnostic/.gitignore b/examples/basic-error-thiserror/.gitignore similarity index 100% rename from examples/basic-error-diagnostic/.gitignore rename to examples/basic-error-thiserror/.gitignore diff --git a/examples/basic-error-diagnostic/Cargo.toml b/examples/basic-error-thiserror/Cargo.toml similarity index 94% rename from examples/basic-error-diagnostic/Cargo.toml rename to examples/basic-error-thiserror/Cargo.toml index b81ef730..d7c7d725 100644 --- a/examples/basic-error-diagnostic/Cargo.toml +++ b/examples/basic-error-thiserror/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "basic-error-diagnostic" +name = "basic-error-thiserror" version = "0.1.0" edition = "2021" diff --git a/examples/basic-error-diagnostic/README.md b/examples/basic-error-thiserror/README.md similarity index 100% rename from examples/basic-error-diagnostic/README.md rename to examples/basic-error-thiserror/README.md diff --git a/examples/basic-error-diagnostic/src/main.rs b/examples/basic-error-thiserror/src/main.rs similarity index 90% rename from examples/basic-error-diagnostic/src/main.rs rename to examples/basic-error-thiserror/src/main.rs index 11f68d4b..403309bf 100644 --- a/examples/basic-error-diagnostic/src/main.rs +++ b/examples/basic-error-thiserror/src/main.rs @@ -13,8 +13,8 @@ pub enum ExecutionError { Unexpected(String), } -impl<'a> From for Diagnostic<'a> { - fn from(value: ExecutionError) -> Diagnostic<'a> { +impl From for Diagnostic { + fn from(value: ExecutionError) -> Diagnostic { let (error_type, error_message) = match value { ExecutionError::DatabaseError(err) => ("Retryable", err.to_string()), ExecutionError::Unexpected(err) => ("NonRetryable", err.to_string()), diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 1165084b..fc822b95 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -22,7 +22,11 @@ apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] -tracing = ["lambda_runtime/tracing"] +tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities +opentelemetry = ["lambda_runtime/opentelemetry"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["lambda_runtime/anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["lambda_runtime/eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["lambda_runtime/miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] base64 = { workspace = true } diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 90e59867..233d6992 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -194,7 +194,7 @@ where S: Service, S::Future: Send + 'a, R: IntoResponse, - E: std::fmt::Debug + for<'b> Into>, + E: std::fmt::Debug + Into, { lambda_runtime::run(Adapter::from(handler)).await } diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index ad3471d3..a93408b4 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -20,7 +20,7 @@ pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), where S: Service, Error = E>, S::Future: Send + 'a, - E: Debug + for<'b> Into>, + E: Debug + Into, B: Body + Unpin + Send + 'static, B::Data: Into + Send, B::Error: Into + Send + Debug, diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 9e56d05b..371b32f7 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -15,13 +15,18 @@ readme = "../README.md" [features] default = ["tracing"] -tracing = ["lambda_runtime_api_client/tracing"] -opentelemetry = ["opentelemetry-semantic-conventions"] +tracing = ["lambda_runtime_api_client/tracing"] # enables access to the Tracing utilities +opentelemetry = ["opentelemetry-semantic-conventions"] # enables access to the OpenTelemetry layers and utilities +anyhow = ["dep:anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info +eyre = ["dep:eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info +miette = ["dep:miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] +anyhow = { version = "1.0.86", optional = true } async-stream = "0.3" base64 = { workspace = true } bytes = { workspace = true } +eyre = { version = "0.6.12", optional = true } futures = { workspace = true } http = { workspace = true } http-body = { workspace = true } @@ -35,6 +40,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.14", optional = true } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index 9a7230a7..c03ce284 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -1,5 +1,5 @@ use serde::{Deserialize, Serialize}; -use std::{any::type_name, borrow::Cow}; +use std::any::type_name; use crate::{deserializer::DeserializeError, Error}; @@ -14,114 +14,144 @@ use crate::{deserializer::DeserializeError, Error}; /// [`error_type`][`Diagnostic::error_type`] is derived from the type name of /// the original error with [`std::any::type_name`] as a fallback, which may /// not be reliable for conditional error handling. -/// You can define your own error container that implements `Into` -/// if you need to handle errors based on error types. +/// +/// To get more descriptive [`error_type`][`Diagnostic::error_type`] fields, you can implement `From` for your error type. +/// That gives you full control on what the `error_type` is. /// /// Example: /// ``` /// use lambda_runtime::{Diagnostic, Error, LambdaEvent}; -/// use std::borrow::Cow; /// /// #[derive(Debug)] -/// struct ErrorResponse(Error); +/// struct ErrorResponse(&'static str); /// -/// impl<'a> Into> for ErrorResponse { -/// fn into(self) -> Diagnostic<'a> { +/// impl From for Diagnostic { +/// fn from(error: ErrorResponse) -> Diagnostic { /// Diagnostic { /// error_type: "MyError".into(), -/// error_message: self.0.to_string().into(), +/// error_message: error.0.to_string(), /// } /// } /// } /// /// async fn function_handler(_event: LambdaEvent<()>) -> Result<(), ErrorResponse> { -/// // ... do something -/// Ok(()) +/// Err(ErrorResponse("this is an error response")) /// } /// ``` #[derive(Debug, Eq, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Diagnostic<'a> { - /// Error type. - /// - /// `error_type` is derived from the type name of the original error with - /// [`std::any::type_name`] as a fallback. - /// Please implement your own `Into` if you need more reliable - /// error types. - pub error_type: Cow<'a, str>, - /// Error message. +pub struct Diagnostic { + /// `error_type` is the type of exception or error returned by the function. + /// Use this field to categorize the different kinds of errors that your function + /// might experience. /// - /// `error_message` is the output from the [`Display`][std::fmt::Display] - /// implementation of the original error as a fallback. - pub error_message: Cow<'a, str>, + /// In standard implementations, `error_type` is derived from the type name of the original error with + /// [`std::any::type_name`], however this is not descriptive enough for an error type. + /// Implement your own `Into` to return a more descriptive error type. + pub error_type: String, + /// `error_message` is a string expression of the error. + /// In standard implementations, it's the output from the [`Display`][std::fmt::Display] + /// implementation of the original error. + pub error_message: String, } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: DeserializeError) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { +impl From for Diagnostic { fn from(value: Error) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a, T> From> for Diagnostic<'a> -where - T: std::error::Error, -{ - fn from(value: Box) -> Self { +impl From> for Diagnostic { + fn from(value: Box) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From> for Diagnostic<'a> { - fn from(value: Box) -> Self { +impl From for Diagnostic { + fn from(value: std::convert::Infallible) -> Self { Diagnostic { - error_type: type_name::>().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: std::convert::Infallible) -> Self { +impl From for Diagnostic { + fn from(value: String) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.to_string().into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From for Diagnostic<'a> { - fn from(value: String) -> Self { +impl From<&'static str> for Diagnostic { + fn from(value: &'static str) -> Self { Diagnostic { - error_type: type_name::().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } -impl<'a> From<&'static str> for Diagnostic<'a> { - fn from(value: &'static str) -> Self { +impl From for Diagnostic { + fn from(value: std::io::Error) -> Self { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "anyhow")] +impl From for Diagnostic { + fn from(value: anyhow::Error) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +#[cfg(feature = "eyre")] +impl From for Diagnostic { + fn from(value: eyre::Report) -> Diagnostic { Diagnostic { - error_type: type_name::<&'static str>().into(), - error_message: value.into(), + error_type: type_name_of_val(&value), + error_message: value.to_string(), } } } +#[cfg(feature = "miette")] +impl From for Diagnostic { + fn from(value: miette::Report) -> Diagnostic { + Diagnostic { + error_type: type_name_of_val(&value), + error_message: value.to_string(), + } + } +} + +pub(crate) fn type_name_of_val(_: T) -> String { + type_name::().into() +} + #[cfg(test)] mod test { use super::*; @@ -141,4 +171,34 @@ mod test { let actual: Value = serde_json::to_value(actual).expect("failed to serialize diagnostic"); assert_eq!(expected, actual); } + + #[cfg(feature = "anyhow")] + #[test] + fn test_anyhow_integration() { + use anyhow::Error as AnyhowError; + let error: AnyhowError = anyhow::anyhow!("anyhow error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&anyhow::Error"); + assert_eq!(diagnostic.error_message, "anyhow error"); + } + + #[cfg(feature = "eyre")] + #[test] + fn test_eyre_integration() { + use eyre::Report; + let error: Report = eyre::eyre!("eyre error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&eyre::Report"); + assert_eq!(diagnostic.error_message, "eyre error"); + } + + #[cfg(feature = "miette")] + #[test] + fn test_miette_integration() { + use miette::Report; + let error: Report = miette::miette!("miette error"); + let diagnostic: Diagnostic = error.into(); + assert_eq!(diagnostic.error_type, "&miette::eyreish::Report"); + assert_eq!(diagnostic.error_message, "miette error"); + } } diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index 7c963f68..e744cde1 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -51,8 +51,7 @@ impl - Service +impl Service for RuntimeApiResponseService< S, EventPayload, @@ -63,7 +62,7 @@ impl<'a, S, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI StreamError, > where - S: Service, Response = Response, Error = Diagnostic<'a>>, + S: Service, Response = Response, Error = Diagnostic>, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -74,7 +73,7 @@ where type Response = http::Request; type Error = BoxError; type Future = - RuntimeApiResponseFuture<'a, S::Future, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError>; + RuntimeApiResponseFuture; fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> task::Poll> { self.inner @@ -120,21 +119,21 @@ where } } -fn build_event_error_request<'a, T>(request_id: &'a str, err: T) -> Result, BoxError> +fn build_event_error_request(request_id: &str, err: T) -> Result, BoxError> where - T: Into> + Debug, + T: Into + Debug, { error!(error = ?err, "Request payload deserialization into LambdaEvent failed. The handler will not be called. Log at TRACE level to see the payload."); EventErrorRequest::new(request_id, err).into_req() } #[pin_project(project = RuntimeApiResponseFutureProj)] -pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> { +pub enum RuntimeApiResponseFuture { Future( #[pin] F, String, PhantomData<( - &'a (), + (), Response, BufferedResponse, StreamingResponse, @@ -145,10 +144,10 @@ pub enum RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingRe Ready(Option, BoxError>>), } -impl<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> Future - for RuntimeApiResponseFuture<'a, F, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +impl Future + for RuntimeApiResponseFuture where - F: Future>>, + F: Future>, Response: IntoFunctionResponse, BufferedResponse: Serialize, StreamingResponse: Stream> + Unpin + Send + 'static, diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 036b747b..c76348ac 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -1,9 +1,7 @@ -use crate::{Diagnostic, LambdaEvent}; +use crate::{diagnostic::type_name_of_val, Diagnostic, LambdaEvent}; use futures::{future::CatchUnwind, FutureExt}; use pin_project::pin_project; -use std::{ - any::Any, borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task, -}; +use std::{any::Any, fmt::Debug, future::Future, marker::PhantomData, panic::AssertUnwindSafe, pin::Pin, task}; use tower::Service; use tracing::error; @@ -33,9 +31,9 @@ impl<'a, S, Payload> Service> for CatchPanicService<'a, S> where S: Service>, S::Future: 'a, - S::Error: Into> + Debug, + S::Error: Into + Debug, { - type Error = Diagnostic<'a>; + type Error = Diagnostic; type Response = S::Response; type Future = CatchPanicFuture<'a, S::Future>; @@ -71,9 +69,9 @@ pub enum CatchPanicFuture<'a, F> { impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> where F: Future>, - E: Into> + Debug, + E: Into + Debug, { - type Output = Result>; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> task::Poll { use task::Poll; @@ -98,20 +96,15 @@ where } impl<'a, F> CatchPanicFuture<'a, F> { - fn build_panic_diagnostic(err: &Box) -> Diagnostic<'a> { - let error_type = type_name_of_val(&err); - let msg = if let Some(msg) = err.downcast_ref::<&str>() { + fn build_panic_diagnostic(err: &Box) -> Diagnostic { + let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; Diagnostic { - error_type: Cow::Borrowed(error_type), - error_message: Cow::Owned(msg), + error_type: type_name_of_val(err), + error_message, } } } - -fn type_name_of_val(_: T) -> &'static str { - std::any::type_name::() -} diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index c3e68cc4..76d0562a 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -112,7 +112,7 @@ pub async fn run(handler: F) -> Result<(), Error> where F: Service, Response = R>, F::Future: Future>, - F::Error: for<'a> Into> + fmt::Debug, + F::Error: Into + fmt::Debug, A: for<'de> Deserialize<'de>, R: IntoFunctionResponse, B: Serialize, diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 0e2eb59a..535f1e8e 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -158,11 +158,11 @@ fn test_event_completion_request() { // /runtime/invocation/{AwsRequestId}/error pub(crate) struct EventErrorRequest<'a> { pub(crate) request_id: &'a str, - pub(crate) diagnostic: Diagnostic<'a>, + pub(crate) diagnostic: Diagnostic, } impl<'a> EventErrorRequest<'a> { - pub(crate) fn new(request_id: &'a str, diagnostic: impl Into>) -> EventErrorRequest<'a> { + pub(crate) fn new(request_id: &'a str, diagnostic: impl Into) -> EventErrorRequest<'a> { EventErrorRequest { request_id, diagnostic: diagnostic.into(), @@ -224,8 +224,8 @@ mod tests { let req = EventErrorRequest { request_id: "id", diagnostic: Diagnostic { - error_type: std::borrow::Cow::Borrowed("InvalidEventDataError"), - error_message: std::borrow::Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }, }; let req = req.into_req().unwrap(); diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 56c8efad..1c676480 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -72,7 +72,7 @@ impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamI where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -207,7 +207,7 @@ fn wrap_handler<'a, F, EventPayload, Response, BufferedResponse, StreamingRespon where F: Service, Response = Response>, F::Future: Future>, - F::Error: Into> + Debug, + F::Error: Into + Debug, EventPayload: for<'de> Deserialize<'de>, Response: IntoFunctionResponse, BufferedResponse: Serialize, @@ -257,7 +257,7 @@ mod endpoint_tests { use httpmock::prelude::*; use lambda_runtime_api_client::Client; - use std::{borrow::Cow, env, sync::Arc}; + use std::{env, sync::Arc}; use tokio_stream::StreamExt; #[tokio::test] @@ -324,8 +324,8 @@ mod endpoint_tests { #[tokio::test] async fn test_error_response() -> Result<(), Error> { let diagnostic = Diagnostic { - error_type: Cow::Borrowed("InvalidEventDataError"), - error_message: Cow::Borrowed("Error parsing event data"), + error_type: "InvalidEventDataError".into(), + error_message: "Error parsing event data".into(), }; let body = serde_json::to_string(&diagnostic)?; From 2708342e6f9d62848f311a962416be1d4454e754 Mon Sep 17 00:00:00 2001 From: mx Date: Sun, 14 Jul 2024 13:05:37 +1200 Subject: [PATCH 147/211] Fixed clippy warnings in lambda-events (#910) --- lambda-events/src/event/cloudwatch_events/cloudtrail.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 3f6bcc3e..fc332306 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -91,7 +91,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-assumed-role.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] @@ -100,7 +100,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-federate.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] @@ -109,7 +109,7 @@ mod tests { let data = include_bytes!("../../fixtures/example-cloudwatch-cloudtrail-unknown-user-auth.json"); let parsed: AWSAPICall = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AWSAPICall = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AWSAPICall = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } From 150c5f0bee9fa81077a20faf402b1289189a22f5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Wed, 24 Jul 2024 20:24:18 -0700 Subject: [PATCH 148/211] Release version 0.13 (#912) - Release the error handling improvements introduced after 0.12. --- lambda-http/Cargo.toml | 4 ++-- lambda-runtime/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index fc822b95..08b70d4e 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.12.0" +version = "0.13.0" authors = [ "David Calavera ", "Harold Sun ", @@ -38,7 +38,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.12.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.13.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 371b32f7..0f63cd47 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.12.0" +version = "0.13.0" authors = [ "David Calavera ", "Harold Sun ", From 2cfef413ea79885912a4d82ecddef7111517c427 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 12 Aug 2024 17:00:27 +0100 Subject: [PATCH 149/211] fix: remove unused struct (#914) --- lambda-runtime/src/requests.rs | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 535f1e8e..729272f2 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -186,23 +186,6 @@ impl<'a> IntoRequest for EventErrorRequest<'a> { } } -// /runtime/init/error -struct InitErrorRequest; - -impl IntoRequest for InitErrorRequest { - fn into_req(self) -> Result, Error> { - let uri = "/2018-06-01/runtime/init/error".to_string(); - let uri = Uri::from_str(&uri)?; - - let req = build_request() - .method(Method::POST) - .uri(uri) - .header("lambda-runtime-function-error-type", "unhandled") - .body(Body::empty())?; - Ok(req) - } -} - #[cfg(test)] mod tests { use super::*; @@ -237,17 +220,4 @@ mod tests { None => false, }); } - - #[test] - fn test_init_error_request() { - let req = InitErrorRequest; - let req = req.into_req().unwrap(); - let expected = Uri::from_static("/2018-06-01/runtime/init/error"); - assert_eq!(req.method(), Method::POST); - assert_eq!(req.uri(), &expected); - assert!(match req.headers().get("User-Agent") { - Some(header) => header.to_str().unwrap().starts_with("aws-lambda-rust/"), - None => false, - }); - } } From 6bbd1c111cf9dd6182bc7817268f884bcc262e8c Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 12 Aug 2024 18:10:14 +0100 Subject: [PATCH 150/211] fix: bedrock tests (#915) --- .../src/event/bedrock_agent_runtime/mod.rs | 29 ++++++++++--------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index cf84d4d3..c1425b85 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -82,31 +82,34 @@ pub struct Agent { #[cfg(test)] mod tests { + + use crate::event::bedrock_agent_runtime::AgentEvent; + #[test] - #[cfg(feature = "bedrock-agent-runtime")] - fn example_bedrock_agent__runtime_event() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + #[cfg(feature = "bedrock_agent_runtime")] + fn example_bedrock_agent_runtime_event() { + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] - #[cfg(feature = "bedrock-agent-runtime")] + #[cfg(feature = "bedrock_agent_runtime")] fn example_bedrock_agent_runtime_event_without_parameters() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event-without-parameters.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } #[test] - #[cfg(feature = "bedrock-agent-runtime")] + #[cfg(feature = "bedrock_agent_runtime")] fn example_bedrock_agent_runtime_event_without_request_body() { - let data = include!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); - let parsed: AgentEvent = serde_json::from_str(&data).unwrap(); + let data = include_bytes!("../../fixtures/example-bedrock-agent-runtime-event-without-request-body.json"); + let parsed: AgentEvent = serde_json::from_slice(data).unwrap(); let output: String = serde_json::to_string(&parsed).unwrap(); - let reparsed: AgentEvent = serde_json::from_slice(&output.as_bytes()).unwrap(); + let reparsed: AgentEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } } From d7c53b2d448421a4a4680c9d77eebb25ccfb9e55 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Tue, 13 Aug 2024 18:10:56 +0100 Subject: [PATCH 151/211] add support for integration test via GitHub Actions (#913) --- .github/workflows/run-integration-test.yml | 62 +++ lambda-integration-tests/Cargo.toml | 30 +- lambda-integration-tests/python/main.py | 4 - lambda-integration-tests/samconfig.toml | 23 ++ lambda-integration-tests/src/authorizer.rs | 53 +++ .../src/bin/extension-fn.rs | 28 -- .../src/bin/extension-trait.rs | 88 ----- lambda-integration-tests/src/bin/http-fn.rs | 26 -- .../src/bin/http-trait.rs | 83 ---- .../src/bin/logs-trait.rs | 75 ---- .../src/bin/runtime-fn.rs | 36 -- .../src/bin/runtime-trait.rs | 92 ----- lambda-integration-tests/src/helloworld.rs | 26 ++ lambda-integration-tests/template.yaml | 353 +++--------------- .../tests/integration_test.rs | 12 + 15 files changed, 240 insertions(+), 751 deletions(-) create mode 100644 .github/workflows/run-integration-test.yml delete mode 100644 lambda-integration-tests/python/main.py create mode 100644 lambda-integration-tests/samconfig.toml create mode 100644 lambda-integration-tests/src/authorizer.rs delete mode 100644 lambda-integration-tests/src/bin/extension-fn.rs delete mode 100644 lambda-integration-tests/src/bin/extension-trait.rs delete mode 100644 lambda-integration-tests/src/bin/http-fn.rs delete mode 100644 lambda-integration-tests/src/bin/http-trait.rs delete mode 100644 lambda-integration-tests/src/bin/logs-trait.rs delete mode 100644 lambda-integration-tests/src/bin/runtime-fn.rs delete mode 100644 lambda-integration-tests/src/bin/runtime-trait.rs create mode 100644 lambda-integration-tests/src/helloworld.rs create mode 100644 lambda-integration-tests/tests/integration_test.rs diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml new file mode 100644 index 00000000..f1e30d09 --- /dev/null +++ b/.github/workflows/run-integration-test.yml @@ -0,0 +1,62 @@ +name: Run integration tests + +permissions: + id-token: write + contents: read + +on: + workflow_dispatch: + push: + +jobs: + run-integration-tests: + runs-on: ubuntu-latest + steps: + - name: install Cargo Lambda + uses: jaxxstorm/action-install-gh-release@v1.9.0 + with: + repo: cargo-lambda/cargo-lambda + platform: linux + arch: x86_64 + - name: install Zig toolchain + uses: korandoru/setup-zig@v1 + with: + zig-version: 0.10.0 + - name: install SAM + uses: aws-actions/setup-sam@v2 + with: + use-installer: true + - uses: actions/checkout@v3 + - name: configure aws credentials + uses: aws-actions/configure-aws-credentials@v4.0.2 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + role-session-name: ${{ secrets.ROLE_SESSION_NAME }} + aws-region: ${{ secrets.AWS_REGION }} + - name: build stack + run: cd lambda-integration-tests && sam build --beta-features + - name: validate stack + run: cd lambda-integration-tests && sam validate --lint + - name: deploy stack + id: deploy_stack + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + run: | + cd lambda-integration-tests + stackName="aws-lambda-rust-integ-test-$GITHUB_RUN_ID" + echo "STACK_NAME=$stackName" >> "$GITHUB_OUTPUT" + echo "Stack name = $stackName" + sam deploy --stack-name "${stackName}" --parameter-overrides "ParameterKey=SecretToken,ParameterValue=${{ secrets.SECRET_TOKEN }}" "ParameterKey=LambdaRole,ParameterValue=${{ secrets.AWS_LAMBDA_ROLE }}" --no-confirm-changeset --no-progressbar > disable_output + TEST_ENDPOINT=$(sam list stack-outputs --stack-name "${stackName}" --output json | jq -r '.[] | .OutputValue') + echo "TEST_ENDPOINT=$TEST_ENDPOINT" >> "$GITHUB_OUTPUT" + - name: run test + env: + SECRET_TOKEN: ${{ secrets.SECRET_TOKEN }} + TEST_ENDPOINT: ${{ steps.deploy_stack.outputs.TEST_ENDPOINT }} + run: cd lambda-integration-tests && cargo test + - name: cleanup + if: always() + env: + AWS_REGION: ${{ secrets.AWS_REGION }} + STACK_NAME: ${{ steps.deploy_stack.outputs.STACK_NAME }} + run: sam delete --stack-name "${STACK_NAME}" --no-prompts diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 1b0fc3ef..ee44a969 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,8 +1,8 @@ [package] -name = "lambda_integration_tests" -version = "0.5.0" -authors = ["Nicolas Moutschen "] -edition = "2018" +name = "aws_lambda_rust_integration_tests" +version = "0.1.0" +authors = ["Maxime David"] +edition = "2021" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" @@ -10,13 +10,21 @@ categories = ["web-programming::http-server"] keywords = ["AWS", "Lambda", "API"] readme = "../README.md" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -lambda_http = { path = "../lambda-http" } lambda_runtime = { path = "../lambda-runtime" } -lambda-extension = { path = "../lambda-extension" } -serde = { version = "1", features = ["derive"] } +aws_lambda_events = { path = "../lambda-events" } +serde_json = "1.0.121" tokio = { version = "1", features = ["full"] } -tracing = { version = "0.1", features = ["log"] } -tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt"] } +serde = { version = "1.0.204", features = ["derive"] } + +[dev-dependencies] +reqwest = { version = "0.12.5", features = ["blocking"] } +openssl = { version = "0.10", features = ["vendored"] } + +[[bin]] +name = "helloworld" +path = "src/helloworld.rs" + +[[bin]] +name = "authorizer" +path = "src/authorizer.rs" diff --git a/lambda-integration-tests/python/main.py b/lambda-integration-tests/python/main.py deleted file mode 100644 index e7e2114b..00000000 --- a/lambda-integration-tests/python/main.py +++ /dev/null @@ -1,4 +0,0 @@ -def handler(event, context): - return { - "message": event["command"].upper() - } \ No newline at end of file diff --git a/lambda-integration-tests/samconfig.toml b/lambda-integration-tests/samconfig.toml new file mode 100644 index 00000000..0212b62a --- /dev/null +++ b/lambda-integration-tests/samconfig.toml @@ -0,0 +1,23 @@ +version = 0.1 + +[default] +[default.build.parameters] +cached = true +parallel = true + +[default.validate.parameters] +lint = true + +[default.deploy.parameters] +capabilities = "CAPABILITY_IAM" +confirm_changeset = true +s3_bucket = "aws-lambda-rust-runtime-integration-testing" + +[default.sync.parameters] +watch = true + +[default.local_start_api.parameters] +warm_containers = "EAGER" + +[default.local_start_lambda.parameters] +warm_containers = "EAGER" \ No newline at end of file diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs new file mode 100644 index 00000000..41ddd2d8 --- /dev/null +++ b/lambda-integration-tests/src/authorizer.rs @@ -0,0 +1,53 @@ +use std::env; + +use aws_lambda_events::{ + apigw::{ApiGatewayCustomAuthorizerPolicy, ApiGatewayCustomAuthorizerResponse}, + event::iam::IamPolicyStatement, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; +use serde::Deserialize; +use serde_json::json; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct APIGatewayCustomAuthorizerRequest { + authorization_token: String, + method_arn: String, +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func( + event: LambdaEvent, +) -> Result { + let expected_token = env::var("SECRET_TOKEN").expect("could not read the secret token"); + if event.payload.authorization_token == expected_token { + return Ok(allow(&event.payload.method_arn)); + } + panic!("token is not valid"); +} + +fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { + let stmt = IamPolicyStatement { + action: vec!["execute-api:Invoke".to_string()], + resource: vec![method_arn.to_owned()], + effect: aws_lambda_events::iam::IamPolicyEffect::Allow, + condition: None, + }; + let policy = ApiGatewayCustomAuthorizerPolicy { + version: Some("2012-10-17".to_string()), + statement: vec![stmt], + }; + ApiGatewayCustomAuthorizerResponse { + principal_id: Some("user".to_owned()), + policy_document: policy, + context: json!({ "hello": "world" }), + usage_identifier_key: None, + } +} diff --git a/lambda-integration-tests/src/bin/extension-fn.rs b/lambda-integration-tests/src/bin/extension-fn.rs deleted file mode 100644 index 5e9ec553..00000000 --- a/lambda-integration-tests/src/bin/extension-fn.rs +++ /dev/null @@ -1,28 +0,0 @@ -use lambda_extension::{service_fn, Error, LambdaEvent, NextEvent}; -use tracing::info; - -async fn my_extension(event: LambdaEvent) -> Result<(), Error> { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension-fn] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - info!("[extension-fn] Request event received: {:?}", e); - } - } - - Ok(()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(service_fn(my_extension)).await -} diff --git a/lambda-integration-tests/src/bin/extension-trait.rs b/lambda-integration-tests/src/bin/extension-trait.rs deleted file mode 100644 index e2c73fa3..00000000 --- a/lambda-integration-tests/src/bin/extension-trait.rs +++ /dev/null @@ -1,88 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_extension::{Error, LambdaEvent, NextEvent, Service}; -use tracing::info; - -struct MyExtension { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyExtension { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyExtension { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyExtension { - type Error = Error; - type Future = Pin>>>; - type Response = (); - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[extension] Service was already ready"); - } else { - info!("[extension] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, event: LambdaEvent) -> Self::Future { - match event.next { - NextEvent::Shutdown(e) => { - info!("[extension] Shutdown event received: {:?}", e); - } - NextEvent::Invoke(e) => { - self.invoke_count += 1; - info!("[extension] Request event {} received: {:?}", self.invoke_count, e); - } - } - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[extension] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[extension] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_extension::run(MyExtension::default()).await -} diff --git a/lambda-integration-tests/src/bin/http-fn.rs b/lambda-integration-tests/src/bin/http-fn.rs deleted file mode 100644 index 8107f423..00000000 --- a/lambda-integration-tests/src/bin/http-fn.rs +++ /dev/null @@ -1,26 +0,0 @@ -use lambda_http::{ext::RequestExt, service_fn, Body, Error, IntoResponse, Request, Response}; -use tracing::info; - -async fn handler(event: Request) -> Result { - let _context = event.lambda_context(); - info!("[http-fn] Received event {} {}", event.method(), event.uri().path()); - - Ok(Response::builder() - .status(200) - .body(Body::from("Hello, world!")) - .unwrap()) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let handler = service_fn(handler); - lambda_http::run(handler).await -} diff --git a/lambda-integration-tests/src/bin/http-trait.rs b/lambda-integration-tests/src/bin/http-trait.rs deleted file mode 100644 index d8e6f74f..00000000 --- a/lambda-integration-tests/src/bin/http-trait.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_http::{ext::RequestExt, Body, Error, Request, Response, Service}; -use tracing::info; - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service for MyHandler { - type Error = Error; - type Future = Pin> + Send>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[http-trait] Service was already ready"); - } else { - info!("[http-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: Request) -> Self::Future { - self.invoke_count += 1; - info!("[http-trait] Received event {}: {:?}", self.invoke_count, request); - info!("[http-trait] Lambda context: {:?}", request.lambda_context()); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[http-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[http-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response::builder() - .status(200) - .body(Body::from("Hello, World!")) - .unwrap()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_http::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/bin/logs-trait.rs b/lambda-integration-tests/src/bin/logs-trait.rs deleted file mode 100644 index b474bc8d..00000000 --- a/lambda-integration-tests/src/bin/logs-trait.rs +++ /dev/null @@ -1,75 +0,0 @@ -use lambda_extension::{Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; -use std::{ - future::{ready, Future}, - pin::Pin, - sync::{ - atomic::{AtomicUsize, Ordering::SeqCst}, - Arc, - }, - task::Poll, -}; -use tracing::info; - -/// Custom log processor that increments a counter for each log record. -/// -/// This is a simple example of a custom log processor that can be used to -/// count the number of log records that are processed. -/// -/// This needs to derive Clone (and store the counter in an Arc) as the runtime -/// could need multiple `Service`s to process the logs. -#[derive(Clone, Default)] -struct MyLogsProcessor { - counter: Arc, -} - -impl MyLogsProcessor { - pub fn new() -> Self { - Self::default() - } -} - -type MyLogsFuture = Pin> + Send>>; - -/// Implementation of the actual log processor -/// -/// This receives a `Vec` whenever there are new log entries available. -impl Service> for MyLogsProcessor { - type Response = (); - type Error = Error; - type Future = MyLogsFuture; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, logs: Vec) -> Self::Future { - let counter = self.counter.fetch_add(1, SeqCst); - for log in logs { - match log.record { - LambdaLogRecord::Function(record) => { - info!("[logs] {} [function] {}: {}", log.time, counter, record.trim()) - } - LambdaLogRecord::Extension(record) => { - info!("[logs] {} [extension] {}: {}", log.time, counter, record.trim()) - } - _ => (), - } - } - - Box::pin(ready(Ok(()))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - let logs_processor = SharedService::new(MyLogsProcessor::new()); - Extension::new().with_logs_processor(logs_processor).run().await?; - - Ok(()) -} diff --git a/lambda-integration-tests/src/bin/runtime-fn.rs b/lambda-integration-tests/src/bin/runtime-fn.rs deleted file mode 100644 index d16717aa..00000000 --- a/lambda-integration-tests/src/bin/runtime-fn.rs +++ /dev/null @@ -1,36 +0,0 @@ -use lambda_runtime::{service_fn, Error, LambdaEvent}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -async fn handler(event: LambdaEvent) -> Result { - info!("[handler-fn] Received event: {:?}", event); - - let (event, _) = event.into_parts(); - - Ok(Response { - message: event.command.to_uppercase(), - }) -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - // The runtime logging can be enabled here by initializing `tracing` with `tracing-subscriber` - // While `tracing` is used internally, `log` can be used as well if preferred. - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(service_fn(handler)).await -} diff --git a/lambda-integration-tests/src/bin/runtime-trait.rs b/lambda-integration-tests/src/bin/runtime-trait.rs deleted file mode 100644 index 0bf31e43..00000000 --- a/lambda-integration-tests/src/bin/runtime-trait.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{ - future::{ready, Future}, - pin::Pin, - sync::atomic::{AtomicBool, Ordering}, -}; - -use lambda_runtime::{Error, LambdaEvent, Service}; -use serde::{Deserialize, Serialize}; -use tracing::info; - -#[derive(Deserialize, Debug)] -struct Request { - command: String, -} - -#[derive(Serialize, Debug)] -struct Response { - message: String, -} - -struct MyHandler { - invoke_count: usize, - ready: AtomicBool, -} - -impl Default for MyHandler { - fn default() -> Self { - Self { - invoke_count: usize::default(), - // New instances are not ready to be called until polled. - ready: false.into(), - } - } -} - -impl Clone for MyHandler { - fn clone(&self) -> Self { - Self { - invoke_count: self.invoke_count, - // Cloned instances may not be immediately ready to be called. - // https://docs.rs/tower/0.4.13/tower/trait.Service.html#be-careful-when-cloning-inner-services - ready: false.into(), - } - } -} - -impl Service> for MyHandler { - type Error = Error; - type Future = Pin>>>; - type Response = Response; - - fn poll_ready(&mut self, _cx: &mut core::task::Context<'_>) -> core::task::Poll> { - if self.ready.swap(true, Ordering::SeqCst) { - info!("[runtime-trait] Service was already ready"); - } else { - info!("[runtime-trait] Service is now ready"); - }; - - core::task::Poll::Ready(Ok(())) - } - - fn call(&mut self, request: LambdaEvent) -> Self::Future { - self.invoke_count += 1; - info!("[runtime-trait] Received event {}: {:?}", self.invoke_count, request); - - // After being called once, the service is no longer ready until polled again. - if self.ready.swap(false, Ordering::SeqCst) { - info!("[runtime-trait] The service is ready"); - } else { - // https://docs.rs/tower/latest/tower/trait.Service.html#backpressure - // https://docs.rs/tower/latest/tower/trait.Service.html#be-careful-when-cloning-inner-services - // > Services are permitted to panic if `call` is invoked without obtaining - // > `Poll::Ready(Ok(()))` from `poll_ready`. - panic!("[runtime-trait] The service is not ready; `.poll_ready()` must be called first"); - } - - Box::pin(ready(Ok(Response { - message: request.payload.command.to_uppercase(), - }))) - } -} - -#[tokio::main] -async fn main() -> Result<(), Error> { - tracing_subscriber::fmt() - .with_max_level(tracing::Level::INFO) - // disabling time is handy because CloudWatch will add the ingestion time. - .without_time() - .init(); - - lambda_runtime::run(MyHandler::default()).await -} diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs new file mode 100644 index 00000000..9989da43 --- /dev/null +++ b/lambda-integration-tests/src/helloworld.rs @@ -0,0 +1,26 @@ +use aws_lambda_events::{ + apigw::{ApiGatewayProxyRequest, ApiGatewayProxyResponse}, + http::HeaderMap, +}; +use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + let func = service_fn(func); + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(_event: LambdaEvent) -> Result { + let mut headers = HeaderMap::new(); + headers.insert("content-type", "text/html".parse().unwrap()); + let resp = ApiGatewayProxyResponse { + status_code: 200, + multi_value_headers: headers.clone(), + is_base64_encoded: false, + body: Some("Hello world!".into()), + headers, + }; + Ok(resp) +} diff --git a/lambda-integration-tests/template.yaml b/lambda-integration-tests/template.yaml index d148c264..1aa69fc8 100644 --- a/lambda-integration-tests/template.yaml +++ b/lambda-integration-tests/template.yaml @@ -1,325 +1,62 @@ AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 +Description: maxday-test + +Parameters: + LambdaRole: + Type: String + SecretToken: + Type: String Globals: Function: - MemorySize: 128 - Handler: bootstrap - Timeout: 5 + Timeout: 3 Resources: - # Rust function using runtime_fn running on AL2023 - RuntimeFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL2 - RuntimeFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using runtime_fn running on AL1 - RuntimeFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-fn/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2023 - RuntimeTraitAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2023 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL2 - RuntimeTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided.al2 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using a Service implementation running on AL1 - RuntimeTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/runtime-trait/ - Runtime: provided - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL2023 - HttpFnAl2023: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ + API: + Type: AWS::Serverless::Api + Properties: + StageName: integ-test + Auth: + DefaultAuthorizer: MyLambdaAuthorizer + Authorizers: + MyLambdaAuthorizer: + FunctionArn: !GetAtt AuthorizerFunction.Arn + HelloWorldFunction: + Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: helloworld + Properties: + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 + Role: !Ref LambdaRole Events: - ApiGet: + HelloWorld: Type: Api Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait + RestApiId: !Ref API + Path: /hello + Method: get - # Rust function using lambda_http::service_fn running on AL2 - HttpFnAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2023 - HttpTraitAl2023: + AuthorizerFunction: Type: AWS::Serverless::Function + Metadata: + BuildMethod: rust-cargolambda + BuildProperties: + Binary: authorizer Properties: - CodeUri: ../build/http-trait/ + CodeUri: ./ + Handler: bootstrap Runtime: provided.al2023 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL2 - HttpTraitAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided.al2 - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /al2-trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /al2-trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /al2-trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /al2-trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http::service_fn running on AL1 - HttpFn: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-fn/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Rust function using lambda_http with Service running on AL1 - HttpTrait: - Type: AWS::Serverless::Function - Properties: - CodeUri: ../build/http-trait/ - Runtime: provided - Events: - ApiGet: - Type: Api - Properties: - Method: GET - Path: /trait/get - ApiPost: - Type: Api - Properties: - Method: POST - Path: /trait/post - ApiV2Get: - Type: HttpApi - Properties: - Method: GET - Path: /trait/get - ApiV2Post: - Type: HttpApi - Properties: - Method: POST - Path: /trait/post - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL2 - PythonAl2: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.9 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - # Python function running on AL1 - Python: - Type: AWS::Serverless::Function - Properties: - CodeUri: ./python/ - Handler: main.handler - Runtime: python3.7 - Layers: - - !Ref LogsTrait - - !Ref ExtensionFn - - !Ref ExtensionTrait - - LogsTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/logs-trait/ - - ExtensionFn: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-fn/ - - ExtensionTrait: - Type: AWS::Serverless::LayerVersion - Properties: - ContentUri: ../build/extension-trait/ + Role: !Ref LambdaRole + Environment: + Variables: + SECRET_TOKEN: !Ref SecretToken Outputs: - RuntimeFnAl2: - Value: !GetAtt RuntimeFnAl2.Arn - RuntimeFn: - Value: !GetAtt RuntimeFn.Arn - RuntimeTraitAl2: - Value: !GetAtt RuntimeTraitAl2.Arn - RuntimeTrait: - Value: !GetAtt RuntimeTrait.Arn - PythonAl2: - Value: !GetAtt PythonAl2.Arn - Python: - Value: !GetAtt Python.Arn - - RestApiUrl: - Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod" - HttpApiUrl: - Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com" \ No newline at end of file + HelloApiEndpoint: + Description: "API Gateway endpoint URL for HelloWorld" + Value: !Sub "https://${API}.execute-api.${AWS::Region}.amazonaws.com/integ-test/hello/" \ No newline at end of file diff --git a/lambda-integration-tests/tests/integration_test.rs b/lambda-integration-tests/tests/integration_test.rs new file mode 100644 index 00000000..557b8e0d --- /dev/null +++ b/lambda-integration-tests/tests/integration_test.rs @@ -0,0 +1,12 @@ +#[test] +fn test_calling_lambda_should_return_200() { + let test_endpoint = std::env::var("TEST_ENDPOINT").expect("could not read TEST_ENDPOINT"); + let secret_token = std::env::var("SECRET_TOKEN").expect("could not read SECRET_TOKEN"); + let client = reqwest::blocking::Client::new(); + let res = client + .get(test_endpoint) + .header("Authorization", secret_token) + .send() + .expect("could not the request"); + assert_eq!(res.status(), 200); +} From 622820458deb0dab6fb2712fbe9c230af960e691 Mon Sep 17 00:00:00 2001 From: msiqueira <42478839+mksiq@users.noreply.github.com> Date: Sat, 24 Aug 2024 22:20:00 -0400 Subject: [PATCH 152/211] Fix trivial typo in docs of Tracing (#918) --- lambda-runtime-api-client/src/tracing.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 51e187e4..bdbdc3d8 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -23,7 +23,7 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; /// if they're configured for your function. /// /// This subscriber sets the logging level based on environment variables: -/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes predecence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes precedence over any other environment variables. /// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. /// - if none of those two variables are set, use `INFO` as the logging level. /// From 27191d0c4f2ce34ce982df7a67822b6a8d023bf8 Mon Sep 17 00:00:00 2001 From: Mohammed Laota <39041283+mlaota@users.noreply.github.com> Date: Wed, 4 Sep 2024 11:50:09 -0500 Subject: [PATCH 153/211] feat: ergonomic improvements to HTTP payload deserialization (#921) BREAKING CHANGE: adds additional methods to the `RequestPayloadExt` trait. implements RFC #917 --- lambda-http/src/ext/request.rs | 525 ++++++++++++++++++++++++++++----- 1 file changed, 458 insertions(+), 67 deletions(-) diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index 11c49012..c56518f6 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -20,6 +20,36 @@ pub enum PayloadError { WwwFormUrlEncoded(SerdeError), } +/// Indicates a problem processing a JSON payload. +#[derive(Debug)] +pub enum JsonPayloadError { + /// Problem deserializing a JSON payload. + Parsing(serde_json::Error), +} + +/// Indicates a problem processing an x-www-form-urlencoded payload. +#[derive(Debug)] +pub enum FormUrlEncodedPayloadError { + /// Problem deserializing an x-www-form-urlencoded payload. + Parsing(SerdeError), +} + +impl From for PayloadError { + fn from(err: JsonPayloadError) -> Self { + match err { + JsonPayloadError::Parsing(inner_err) => PayloadError::Json(inner_err), + } + } +} + +impl From for PayloadError { + fn from(err: FormUrlEncodedPayloadError) -> Self { + match err { + FormUrlEncodedPayloadError::Parsing(inner_err) => PayloadError::WwwFormUrlEncoded(inner_err), + } + } +} + impl fmt::Display for PayloadError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -41,55 +71,7 @@ impl Error for PayloadError { } } -/// Extensions for `lambda_http::Request` structs. -/// -/// # Examples -/// -/// A request's body can be deserialized if its correctly encoded as per -/// the request's `Content-Type` header. The two supported content types are -/// `application/x-www-form-urlencoded` and `application/json`. -/// -/// The following handler will work an http request body of `x=1&y=2` -/// as well as `{"x":1, "y":2}` respectively. -/// -/// ```rust,no_run -/// use lambda_http::{ -/// service_fn, Body, Context, Error, IntoResponse, Request, RequestPayloadExt, Response, -/// }; -/// use serde::Deserialize; -/// -/// #[derive(Debug, Default, Deserialize)] -/// struct Args { -/// #[serde(default)] -/// x: usize, -/// #[serde(default)] -/// y: usize -/// } -/// -/// #[tokio::main] -/// async fn main() -> Result<(), Error> { -/// lambda_http::run(service_fn(add)).await?; -/// Ok(()) -/// } -/// -/// async fn add( -/// request: Request -/// ) -> Result, Error> { -/// let args: Args = request.payload() -/// .unwrap_or_else(|_parse_err| None) -/// .unwrap_or_default(); -/// Ok( -/// Response::new( -/// format!( -/// "{} + {} = {}", -/// args.x, -/// args.y, -/// args.x + args.y -/// ).into() -/// ) -/// ) -/// } -/// ``` +/// Extends `http::Request` with payload deserialization helpers. pub trait RequestPayloadExt { /// Return the result of a payload parsed into a type that implements [`serde::Deserialize`] /// @@ -97,11 +79,148 @@ pub trait RequestPayloadExt { /// and `application/json` flavors of content type /// are supported /// - /// A [`PayloadError`] will be returned for undeserializable - /// payloads. If no body is provided, `Ok(None)` will be returned. + /// A [`PayloadError`] will be returned for undeserializable payloads. + /// If no body is provided, the content-type header is missing, + /// or the content-type header is unsupported, then `Ok(None)` will + /// be returned. Note that a blank body (e.g. an empty string) is treated + /// like a present payload by some deserializers and may result in an error. + /// + /// ### Examples + /// + /// A request's body can be deserialized if its correctly encoded as per + /// the request's `Content-Type` header. The two supported content types are + /// `application/x-www-form-urlencoded` and `application/json`. + /// + /// The following handler will work an http request body of `x=1&y=2` + /// as well as `{"x":1, "y":2}` respectively. + /// + /// ```rust,no_run + /// use lambda_http::{ + /// service_fn, Body, Context, Error, IntoResponse, Request, RequestPayloadExt, Response, + /// }; + /// use serde::Deserialize; + /// + /// #[derive(Debug, Default, Deserialize)] + /// struct Args { + /// #[serde(default)] + /// x: usize, + /// #[serde(default)] + /// y: usize + /// } + /// + /// #[tokio::main] + /// async fn main() -> Result<(), Error> { + /// lambda_http::run(service_fn(add)).await?; + /// Ok(()) + /// } + /// + /// async fn add( + /// request: Request + /// ) -> Result, Error> { + /// let args: Args = request.payload() + /// .unwrap_or_else(|_parse_err| None) + /// .unwrap_or_default(); + /// Ok( + /// Response::new( + /// format!( + /// "{} + {} = {}", + /// args.x, + /// args.y, + /// args.x + args.y + /// ).into() + /// ) + /// ) + /// } fn payload(&self) -> Result, PayloadError> where D: DeserializeOwned; + + /// Attempts to deserialize the request payload as JSON. When there is no payload, + /// `Ok(None)` is returned. + /// + /// ### Errors + /// + /// If a present payload is not a valid JSON payload matching the annotated type, + /// a [`JsonPayloadError`] is returned. + /// + /// ### Examples + /// + /// #### 1. Parsing a JSONString. + /// ```ignore + /// let req = http::Request::builder() + /// .body(Body::from("\"I am a JSON string\"")) + /// .expect("failed to build request"); + /// match req.json::() { + /// Ok(Some(json)) => assert_eq!(json, "I am a JSON string"), + /// Ok(None) => panic!("payload is missing."), + /// Err(err) => panic!("error processing json: {err:?}"), + /// } + /// ``` + /// + /// #### 2. Parsing a JSONObject. + /// ```ignore + /// #[derive(Deserialize, Eq, PartialEq, Debug)] + /// struct Person { + /// name: String, + /// age: u8, + /// } + /// + /// let req = http::Request::builder() + /// .body(Body::from(r#"{"name": "Adam", "age": 23}"#)) + /// .expect("failed to build request"); + /// + /// match req.json::() { + /// Ok(Some(person)) => assert_eq!( + /// person, + /// Person { + /// name: "Adam".to_string(), + /// age: 23 + /// } + /// ), + /// Ok(None) => panic!("payload is missing"), + /// Err(JsonPayloadError::Parsing(err)) => { + /// if err.is_data() { + /// panic!("payload does not match Person schema: {err:?}") + /// } + /// if err.is_syntax() { + /// panic!("payload is invalid json: {err:?}") + /// } + /// panic!("failed to parse json: {err:?}") + /// } + /// } + /// ``` + fn json(&self) -> Result, JsonPayloadError> + where + D: DeserializeOwned; + + /// Attempts to deserialize the request payload as an application/x-www-form-urlencoded + /// content type. When there is no payload, `Ok(None)` is returned. + /// + /// ### Errors + /// + /// If a present payload is not a valid application/x-www-form-urlencoded payload + /// matching the annotated type, a [`FormUrlEncodedPayloadError`] is returned. + /// + /// ### Examples + /// ```ignore + /// let req = http::Request::builder() + /// .body(Body::from("name=Adam&age=23")) + /// .expect("failed to build request"); + /// match req.form_url_encoded::() { + /// Ok(Some(person)) => assert_eq!( + /// person, + /// Person { + /// name: "Adam".to_string(), + /// age: 23 + /// } + /// ), + /// Ok(None) => panic!("payload is missing."), + /// Err(err) => panic!("error processing payload: {err:?}"), + /// } + /// ``` + fn form_url_encoded(&self) -> Result, FormUrlEncodedPayloadError> + where + D: DeserializeOwned; } impl RequestPayloadExt for http::Request { @@ -114,28 +233,47 @@ impl RequestPayloadExt for http::Request { .map(|ct| match ct.to_str() { Ok(content_type) => { if content_type.starts_with("application/x-www-form-urlencoded") { - return serde_urlencoded::from_bytes::(self.body().as_ref()) - .map_err(PayloadError::WwwFormUrlEncoded) - .map(Some); + return self.form_url_encoded().map_err(PayloadError::from); } else if content_type.starts_with("application/json") { - return serde_json::from_slice::(self.body().as_ref()) - .map_err(PayloadError::Json) - .map(Some); + return self.json().map_err(PayloadError::from); } - Ok(None) } _ => Ok(None), }) .unwrap_or_else(|| Ok(None)) } + + fn json(&self) -> Result, JsonPayloadError> + where + D: DeserializeOwned, + { + if self.body().is_empty() { + return Ok(None); + } + serde_json::from_slice::(self.body().as_ref()) + .map(Some) + .map_err(JsonPayloadError::Parsing) + } + + fn form_url_encoded(&self) -> Result, FormUrlEncodedPayloadError> + where + D: DeserializeOwned, + { + if self.body().is_empty() { + return Ok(None); + } + serde_urlencoded::from_bytes::(self.body().as_ref()) + .map(Some) + .map_err(FormUrlEncodedPayloadError::Parsing) + } } #[cfg(test)] mod tests { use serde::Deserialize; - use super::RequestPayloadExt; + use super::{FormUrlEncodedPayloadError, JsonPayloadError, RequestPayloadExt}; use crate::Body; @@ -145,6 +283,20 @@ mod tests { baz: usize, } + fn get_test_payload_as_json_body() -> Body { + Body::from(r#"{"foo":"bar", "baz": 2}"#) + } + + fn assert_eq_test_payload(payload: Option) { + assert_eq!( + payload, + Some(Payload { + foo: "bar".into(), + baz: 2 + }) + ); + } + #[test] fn requests_have_form_post_parsable_payloads() { let request = http::Request::builder() @@ -165,16 +317,10 @@ mod tests { fn requests_have_json_parsable_payloads() { let request = http::Request::builder() .header("Content-Type", "application/json") - .body(Body::from(r#"{"foo":"bar", "baz": 2}"#)) + .body(get_test_payload_as_json_body()) .expect("failed to build request"); let payload: Option = request.payload().unwrap_or_default(); - assert_eq!( - payload, - Some(Payload { - foo: "bar".into(), - baz: 2 - }) - ); + assert_eq_test_payload(payload) } #[test] @@ -217,4 +363,249 @@ mod tests { let payload: Option = request.payload().unwrap_or_default(); assert_eq!(payload, None); } + + #[test] + fn requests_omitting_body_returns_none() { + let request = http::Request::builder() + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[test] + fn requests_with_json_content_type_hdr_omitting_body_returns_none() { + let request = http::Request::builder() + .header("Content-Type", "application/json; charset=UTF-8") + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[test] + fn requests_with_formurlencoded_content_type_hdr_omitting_body_returns_none() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") + .body(Body::Empty) + .expect("failed to build request"); + let payload: Option = request.payload().unwrap(); + assert_eq!(payload, None) + } + + #[derive(Deserialize, Eq, PartialEq, Debug)] + struct Person { + name: String, + age: u8, + } + + #[test] + fn json_fn_parses_json_strings() { + let req = http::Request::builder() + .body(Body::from("\"I am a JSON string\"")) + .expect("failed to build request"); + match req.json::() { + Ok(Some(json)) => assert_eq!(json, "I am a JSON string"), + Ok(None) => panic!("payload is missing."), + Err(err) => panic!("error processing json: {err:?}"), + } + } + + #[test] + fn json_fn_parses_objects() { + let req = http::Request::builder() + .body(Body::from(r#"{"name": "Adam", "age": 23}"#)) + .expect("failed to build request"); + + match req.json::() { + Ok(Some(person)) => assert_eq!( + person, + Person { + name: "Adam".to_string(), + age: 23 + } + ), + Ok(None) => panic!("request data missing"), + Err(JsonPayloadError::Parsing(err)) => { + if err.is_data() { + panic!("payload does not match Person: {err:?}") + } + if err.is_syntax() { + panic!("invalid json: {err:?}") + } + panic!("failed to parse json: {err:?}") + } + } + } + + #[test] + fn json_fn_parses_list_of_objects() { + let req = http::Request::builder() + .body(Body::from( + r#"[{"name": "Adam", "age": 23}, {"name": "Sarah", "age": 47}]"#, + )) + .expect("failed to build request"); + let expected_result = vec![ + Person { + name: "Adam".to_string(), + age: 23, + }, + Person { + name: "Sarah".to_string(), + age: 47, + }, + ]; + let result: Vec = req.json().expect("invalid payload").expect("missing payload"); + assert_eq!(result, expected_result); + } + + #[test] + fn json_fn_parses_nested_objects() { + #[derive(Deserialize, Eq, PartialEq, Debug)] + struct Pet { + name: String, + owner: Person, + } + + let req = http::Request::builder() + .body(Body::from( + r#"{"name": "Gumball", "owner": {"name": "Adam", "age": 23}}"#, + )) + .expect("failed to build request"); + + let expected_result = Pet { + name: "Gumball".to_string(), + owner: Person { + name: "Adam".to_string(), + age: 23, + }, + }; + let result: Pet = req.json().expect("invalid payload").expect("missing payload"); + assert_eq!(result, expected_result); + } + + #[test] + fn json_fn_accepts_request_with_content_type_header() { + let request = http::Request::builder() + .header("Content-Type", "application/json") + .body(get_test_payload_as_json_body()) + .expect("failed to build request"); + let payload: Option = request.json().unwrap(); + assert_eq_test_payload(payload) + } + + #[test] + fn json_fn_accepts_request_without_content_type_header() { + let request = http::Request::builder() + .body(get_test_payload_as_json_body()) + .expect("failed to build request"); + let payload: Option = request.json().expect("failed to parse json"); + assert_eq_test_payload(payload) + } + + #[test] + fn json_fn_given_nonjson_payload_returns_syntax_error() { + let request = http::Request::builder() + .body(Body::Text(String::from("Not a JSON"))) + .expect("failed to build request"); + let payload = request.json::(); + assert!(payload.is_err()); + + if let Err(JsonPayloadError::Parsing(err)) = payload { + assert!(err.is_syntax()) + } else { + panic!( + "{}", + format!("payload should have caused a parsing error. instead, it was {payload:?}") + ); + } + } + + #[test] + fn json_fn_given_unexpected_payload_shape_returns_data_error() { + let request = http::Request::builder() + .body(Body::from(r#"{"foo":"bar", "baz": "!SHOULD BE A NUMBER!"}"#)) + .expect("failed to build request"); + let result = request.json::(); + + if let Err(JsonPayloadError::Parsing(err)) = result { + assert!(err.is_data()) + } else { + panic!( + "{}", + format!("payload should have caused a parsing error. instead, it was {result:?}") + ); + } + } + + #[test] + fn json_fn_given_empty_payload_returns_none() { + let empty_request = http::Request::default(); + let payload: Option = empty_request.json().expect("failed to parse json"); + assert_eq!(payload, None) + } + + #[test] + fn form_url_encoded_fn_parses_forms() { + let req = http::Request::builder() + .body(Body::from("name=Adam&age=23")) + .expect("failed to build request"); + match req.form_url_encoded::() { + Ok(Some(person)) => assert_eq!( + person, + Person { + name: "Adam".to_string(), + age: 23 + } + ), + Ok(None) => panic!("payload is missing."), + Err(err) => panic!("error processing payload: {err:?}"), + } + } + + #[test] + fn form_url_encoded_fn_accepts_request_with_content_type_header() { + let request = http::Request::builder() + .header("Content-Type", "application/x-www-form-urlencoded") + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.form_url_encoded().unwrap(); + assert_eq_test_payload(payload); + } + + #[test] + fn form_url_encoded_fn_accepts_request_without_content_type_header() { + let request = http::Request::builder() + .body(Body::from("foo=bar&baz=2")) + .expect("failed to build request"); + let payload: Option = request.form_url_encoded().expect("failed to parse form"); + assert_eq_test_payload(payload); + } + + #[test] + fn form_url_encoded_fn_given_non_form_urlencoded_payload_errors() { + let request = http::Request::builder() + .body(Body::Text(String::from("Not a url-encoded form"))) + .expect("failed to build request"); + let payload = request.form_url_encoded::(); + assert!(payload.is_err()); + assert!(matches!(payload, Err(FormUrlEncodedPayloadError::Parsing(_)))); + } + + #[test] + fn form_url_encoded_fn_given_unexpected_payload_shape_errors() { + let request = http::Request::builder() + .body(Body::from("foo=bar&baz=SHOULD_BE_A_NUMBER")) + .expect("failed to build request"); + let result = request.form_url_encoded::(); + assert!(result.is_err()); + assert!(matches!(result, Err(FormUrlEncodedPayloadError::Parsing(_)))); + } + + #[test] + fn form_url_encoded_fn_given_empty_payload_returns_none() { + let empty_request = http::Request::default(); + let payload: Option = empty_request.form_url_encoded().expect("failed to parse form"); + assert_eq!(payload, None); + } } From 20206d70b4baef445e6d196e29e6496e21f95c81 Mon Sep 17 00:00:00 2001 From: Martin Bartlett Date: Mon, 16 Sep 2024 19:38:39 +0200 Subject: [PATCH 154/211] Use event filter (#925) * Use LevelFilter instead of Level Originally a `Level` was parsed from one of two environment variables (or defaulted) and then converted into a `LevelFilter` before initializing the subscriber. However, this precludes using `RUST_LOG=off` since `Level` does not recognize that as valid, resulting in `Level::INFO` (the default) being used. `LevelFilter` (to which the above is converted anyway) _does_ allow the value to be `off` - so it seems a little more flexible (and very very minutely faster) to parse the env var or default value directly into a `LevelFilter`. * rustfmt --------- Co-authored-by: Martin Bartlett --- lambda-runtime-api-client/src/tracing.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index bdbdc3d8..09f41991 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -33,14 +33,15 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; pub fn init_default_subscriber() { let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); - let log_level = Level::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(Level::INFO); + let log_level = + LevelFilter::from_str(log_level_str.as_deref().unwrap_or(DEFAULT_LOG_LEVEL)).unwrap_or(LevelFilter::INFO); let collector = tracing_subscriber::fmt() .with_target(false) .without_time() .with_env_filter( EnvFilter::builder() - .with_default_directive(LevelFilter::from_level(log_level).into()) + .with_default_directive(log_level.into()) .from_env_lossy(), ); From 8572af6a5777ac190278d567c5ccba6a56929860 Mon Sep 17 00:00:00 2001 From: Mats Jun Date: Wed, 18 Sep 2024 04:53:54 +0200 Subject: [PATCH 155/211] fix: expose CloudWatchMetricAlarm and CloudWatchCompositeAlarm (#926) * fix: expose CloudWatchMetricAlarm and CloudWatchCompositeAlarm These types are referred to by the CloudWatchAlarm type, but they previously not exported by the crate and left unused in its module. * chore: apply rustfmt --- lambda-events/src/event/cloudwatch_alarms/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 5c4b704d..01174566 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -36,12 +36,11 @@ where } /// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. -#[allow(unused)] -type CloudWatchMetricAlarm = CloudWatchAlarm; +pub type CloudWatchMetricAlarm = + CloudWatchAlarm; /// `CloudWatchCompositeAlarm` is the structure of an event triggered by CloudWatch composite alarms. -#[allow(unused)] -type CloudWatchCompositeAlarm = +pub type CloudWatchCompositeAlarm = CloudWatchAlarm; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] From c3575f69aa49a035e5b2e72c02bea7ff7228492f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 23 Sep 2024 07:51:58 -0700 Subject: [PATCH 156/211] Add AppConfig feature flags example (#928) This example shows how to integrate AppConfig with Rust Lambda functions. It includes CDK constructs to deploy the basic AppConfig scaffolding, and the AppConfig Lambda extension to reduce the latency fetching the AppConfig configuration. --- .gitignore | 3 + .../.gitignore | 1 + .../Cargo.toml | 24 + .../README.md | 65 + .../cdk/.npmignore | 6 + .../cdk/README.md | 14 + .../cdk/bin/cdk.ts | 21 + .../cdk/cdk.json | 72 + .../cdk/jest.config.js | 8 + .../cdk/lib/cdk-stack.ts | 110 + .../cdk/package-lock.json | 7550 +++++++++++++++++ .../cdk/package.json | 28 + .../cdk/test/cdk.test.ts | 17 + .../cdk/tsconfig.json | 31 + .../src/appconfig.rs | 88 + .../src/main.rs | 126 + 16 files changed, 8164 insertions(+) create mode 100644 examples/advanced-appconfig-feature-flags/.gitignore create mode 100644 examples/advanced-appconfig-feature-flags/Cargo.toml create mode 100644 examples/advanced-appconfig-feature-flags/README.md create mode 100644 examples/advanced-appconfig-feature-flags/cdk/.npmignore create mode 100644 examples/advanced-appconfig-feature-flags/cdk/README.md create mode 100644 examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/cdk.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/jest.config.js create mode 100644 examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/package-lock.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/package.json create mode 100644 examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts create mode 100644 examples/advanced-appconfig-feature-flags/cdk/tsconfig.json create mode 100644 examples/advanced-appconfig-feature-flags/src/appconfig.rs create mode 100644 examples/advanced-appconfig-feature-flags/src/main.rs diff --git a/.gitignore b/.gitignore index d8feb244..d5e188a4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ output.json .aws-sam build .vscode + +node_modules +cdk.out diff --git a/examples/advanced-appconfig-feature-flags/.gitignore b/examples/advanced-appconfig-feature-flags/.gitignore new file mode 100644 index 00000000..c41cc9e3 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/.gitignore @@ -0,0 +1 @@ +/target \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/Cargo.toml b/examples/advanced-appconfig-feature-flags/Cargo.toml new file mode 100644 index 00000000..52ebb843 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "lambda-appconfig" +version = "0.1.0" +edition = "2021" + +# Starting in Rust 1.62 you can use `cargo add` to add dependencies +# to your project. +# +# If you're using an older Rust version, +# download cargo-edit(https://github.com/killercup/cargo-edit#installation) +# to install the `add` subcommand. +# +# Running `cargo add DEPENDENCY_NAME` will +# add the latest version of a dependency to the list, +# and it will keep the alphabetic ordering for you. + +[dependencies] +async-trait = "0.1.68" +lambda_runtime = "0.13" +reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" +thiserror = "1.0" +tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-appconfig-feature-flags/README.md b/examples/advanced-appconfig-feature-flags/README.md new file mode 100644 index 00000000..3a95ec9f --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/README.md @@ -0,0 +1,65 @@ +# Rust Lambda with AppConfig Feature Flag + +This project demonstrates a Rust-based AWS Lambda function that uses AWS AppConfig for feature flagging. The function is deployed using AWS CDK and includes automatic rollback capabilities based on error rates. + +## Lambda Function (src/main.rs) + +The Lambda function is written in Rust and does the following: + +1. Integrates with AWS AppConfig to fetch configuration at runtime. +2. Uses a feature flag to determine whether to respond in Spanish. +3. Processes incoming events. +4. Returns a response based on the event and the current feature flag state. + +The function is designed to work with the AWS AppConfig Extension for Lambda, allowing for efficient configuration retrieval. + +## Deployment (cdk directory) + +The project uses AWS CDK for infrastructure as code and deployment. To deploy the project: + +1. Ensure you have the AWS CDK CLI installed and configured. +2. Navigate to the `cdk` directory. +3. Install dependencies: + ``` + npm install + ``` +4. Build the CDK stack: + ``` + npm run build + ``` +5. Deploy the stack: + ``` + cdk deploy + ``` + +## AWS Resources (cdk/lib/cdk-stack.ts) + +The CDK stack creates the following AWS resources: + +1. **AppConfig Application**: Named "MyRustLambdaApp", this is the container for your configuration and feature flags. + +2. **AppConfig Environment**: A "Production" environment is created within the application. + +3. **AppConfig Configuration Profile**: Defines the schema and validation for your configuration. + +4. **AppConfig Hosted Configuration Version**: Contains the actual configuration data, including the "spanish-response" feature flag. + +5. **AppConfig Deployment Strategy**: Defines how configuration changes are rolled out. + +6. **Lambda Function**: A Rust-based function that uses the AppConfig configuration. + - Uses the AWS AppConfig Extension Layer for efficient configuration retrieval. + - Configured with ARM64 architecture and 128MB of memory. + - 30-second timeout. + +7. **CloudWatch Alarm**: Monitors the Lambda function's error rate. + - Triggers if there are more than 5 errors per minute. + +8. **AppConfig Deployment**: Connects all AppConfig components and includes a rollback trigger based on the CloudWatch alarm. + +9. **IAM Role**: Grants the Lambda function permissions to interact with AppConfig and CloudWatch. + +This setup allows for feature flagging with automatic rollback capabilities, ensuring rapid and safe deployment of new features or configurations. + +## Usage + +After deployment, you can update the feature flag in AppConfig to control the Lambda function's behavior. The function will automatically fetch the latest configuration, and if error rates exceed the threshold, AppConfig will automatically roll back to the previous stable configuration. diff --git a/examples/advanced-appconfig-feature-flags/cdk/.npmignore b/examples/advanced-appconfig-feature-flags/cdk/.npmignore new file mode 100644 index 00000000..c1d6d45d --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/.npmignore @@ -0,0 +1,6 @@ +*.ts +!*.d.ts + +# CDK asset staging directory +.cdk.staging +cdk.out diff --git a/examples/advanced-appconfig-feature-flags/cdk/README.md b/examples/advanced-appconfig-feature-flags/cdk/README.md new file mode 100644 index 00000000..9315fe5b --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/README.md @@ -0,0 +1,14 @@ +# Welcome to your CDK TypeScript project + +This is a blank project for CDK development with TypeScript. + +The `cdk.json` file tells the CDK Toolkit how to execute your app. + +## Useful commands + +* `npm run build` compile typescript to js +* `npm run watch` watch for changes and compile +* `npm run test` perform the jest unit tests +* `npx cdk deploy` deploy this stack to your default AWS account/region +* `npx cdk diff` compare deployed stack with current state +* `npx cdk synth` emits the synthesized CloudFormation template diff --git a/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts b/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts new file mode 100644 index 00000000..1d8bfd97 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/bin/cdk.ts @@ -0,0 +1,21 @@ +#!/usr/bin/env node +import 'source-map-support/register'; +import * as cdk from 'aws-cdk-lib'; +import { CdkStack } from '../lib/cdk-stack'; + +const app = new cdk.App(); +new CdkStack(app, 'CdkStack', { + /* If you don't specify 'env', this stack will be environment-agnostic. + * Account/Region-dependent features and context lookups will not work, + * but a single synthesized template can be deployed anywhere. */ + + /* Uncomment the next line to specialize this stack for the AWS Account + * and Region that are implied by the current CLI configuration. */ + // env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION }, + + /* Uncomment the next line if you know exactly what Account and Region you + * want to deploy the stack to. */ + // env: { account: '123456789012', region: 'us-east-1' }, + + /* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */ +}); \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/cdk/cdk.json b/examples/advanced-appconfig-feature-flags/cdk/cdk.json new file mode 100644 index 00000000..87963c5f --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/cdk.json @@ -0,0 +1,72 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "@aws-cdk/core:validateSnapshotRemovalPolicy": true, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, + "@aws-cdk/core:enablePartitionLiterals": true, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": true, + "@aws-cdk/aws-iam:standardizedServicePrincipals": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true, + "@aws-cdk/aws-route53-patters:useCertificate": true, + "@aws-cdk/customresources:installLatestAwsSdkDefault": false, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true, + "@aws-cdk/aws-redshift:columnId": true, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": true, + "@aws-cdk/aws-kms:aliasNameRef": true, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": true, + "@aws-cdk/aws-efs:denyAnonymousAccess": true, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true, + "@aws-cdk/aws-eks:nodegroupNameAttribute": true, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/jest.config.js b/examples/advanced-appconfig-feature-flags/cdk/jest.config.js new file mode 100644 index 00000000..08263b89 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/jest.config.js @@ -0,0 +1,8 @@ +module.exports = { + testEnvironment: 'node', + roots: ['/test'], + testMatch: ['**/*.test.ts'], + transform: { + '^.+\\.tsx?$': 'ts-jest' + } +}; diff --git a/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts b/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts new file mode 100644 index 00000000..3e76deb9 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/lib/cdk-stack.ts @@ -0,0 +1,110 @@ +import * as cdk from 'aws-cdk-lib'; +import * as appconfig from 'aws-cdk-lib/aws-appconfig'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch'; +import { Construct } from 'constructs'; +import { RustFunction } from 'cargo-lambda-cdk'; + +export class CdkStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Create AppConfig Application + const application = new appconfig.CfnApplication(this, 'MyApplication', { + name: 'MyRustLambdaApp', + }); + + // Create AppConfig Environment + const environment = new appconfig.CfnEnvironment(this, 'MyEnvironment', { + applicationId: application.ref, + name: 'Production', + }); + + // Create AppConfig Configuration Profile + const configProfile = new appconfig.CfnConfigurationProfile(this, 'MyConfigProfile', { + applicationId: application.ref, + name: 'MyConfigProfile', + locationUri: 'hosted', + }); + + // Create AppConfig Hosted Configuration Version + const hostedConfig = new appconfig.CfnHostedConfigurationVersion(this, 'MyHostedConfig', { + applicationId: application.ref, + configurationProfileId: configProfile.ref, + content: JSON.stringify({ + 'spanish-response': false + }), + contentType: 'application/json', + }); + + // Create AppConfig Deployment Strategy + const deploymentStrategy = new appconfig.CfnDeploymentStrategy(this, 'MyDeploymentStrategy', { + name: 'MyDeploymentStrategy', + deploymentDurationInMinutes: 0, + growthFactor: 100, + replicateTo: 'NONE', + }); + + const architecture = lambda.Architecture.ARM_64; + const layerVersion = architecture === lambda.Architecture.ARM_64 ? '68' : '60'; + + // Create Lambda function using cargo-lambda-cdk + const myFunction = new RustFunction(this, 'MyRustFunction', { + functionName: 'my-rust-lambda', + manifestPath: '..', // Points to the parent directory where Cargo.toml is located + architecture, + memorySize: 128, + timeout: cdk.Duration.seconds(30), + environment: { + APPLICATION_ID: application.ref, + ENVIRONMENT_ID: environment.ref, + CONFIGURATION_PROFILE_ID: configProfile.ref, + AWS_APPCONFIG_EXTENSION_PREFETCH_LIST: `/applications/${application.ref}/environments/${environment.ref}/configurations/${configProfile.ref}`, + }, + layers: [ + lambda.LayerVersion.fromLayerVersionArn( + this, + 'AppConfigExtensionLayer', + `arn:aws:lambda:${this.region}:027255383542:layer:AWS-AppConfig-Extension:${layerVersion}` + ), + ], + }); + + // Create CloudWatch Alarm for rollback + const errorRateAlarm = new cloudwatch.Alarm(this, 'ErrorRateAlarm', { + metric: myFunction.metricErrors({ + period: cdk.Duration.minutes(1), + statistic: 'sum', + }), + threshold: 5, + evaluationPeriods: 1, + comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD, + alarmDescription: 'Alarm if the error rate is greater than 5 errors per minute', + }); + + // Create AppConfig Deployment with rollback configuration + new appconfig.CfnDeployment(this, 'MyDeployment', { + applicationId: application.ref, + environmentId: environment.ref, + deploymentStrategyId: deploymentStrategy.ref, + configurationProfileId: configProfile.ref, + configurationVersion: hostedConfig.ref, + tags: [ + { + key: 'RollbackTrigger', + value: errorRateAlarm.alarmArn, + }, + ], + }); + + // Grant AppConfig permissions to the Lambda function + myFunction.addToRolePolicy(new cdk.aws_iam.PolicyStatement({ + actions: [ + 'appconfig:GetConfiguration', + 'appconfig:StartConfigurationSession', + 'cloudwatch:PutMetricData', + ], + resources: ['*'], + })); + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json new file mode 100644 index 00000000..61c9a537 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json @@ -0,0 +1,7550 @@ +{ + "name": "cdk", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "cdk", + "version": "0.1.0", + "dependencies": { + "aws-cdk-lib": "2.159.1", + "cargo-lambda-cdk": "^0.0.22", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + }, + "bin": { + "cdk": "bin/cdk.js" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "20.14.2", + "aws-cdk": "2.159.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", + "typescript": "~5.4.5" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@aws-cdk/asset-awscli-v1": { + "version": "2.2.203", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", + "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" + }, + "node_modules/@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "36.3.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", + "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "dependencies": { + "jsonschema": "^1.4.1", + "semver": "^7.6.3" + }, + "engines": { + "node": ">= 18.18.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "dependencies": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "node_modules/@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "node_modules/aws-cdk": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.159.1.tgz", + "integrity": "sha512-bkJOxic/NpJYQCF3MQhfyJVlFtIzMJeVGZp9jZa7TczxJp79Q/TNKzVJYv6GFabNS1wglGPfWkFB/rIJlRhJkg==", + "dev": true, + "bin": { + "cdk": "bin/cdk" + }, + "engines": { + "node": ">= 14.15.0" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/aws-cdk-lib": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", + "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "bundleDependencies": [ + "@balena/dockerignore", + "case", + "fs-extra", + "ignore", + "jsonschema", + "minimatch", + "punycode", + "semver", + "table", + "yaml", + "mime-types" + ], + "dependencies": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "constructs": "^10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/@balena/dockerignore": { + "version": "1.0.2", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/aws-cdk-lib/node_modules/ajv": { + "version": "8.17.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-regex": { + "version": "5.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/ansi-styles": { + "version": "4.3.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/astral-regex": { + "version": "2.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/balanced-match": { + "version": "1.0.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/brace-expansion": { + "version": "1.1.11", + "inBundle": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/aws-cdk-lib/node_modules/case": { + "version": "1.6.3", + "inBundle": true, + "license": "(MIT OR GPL-3.0-or-later)", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-convert": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/color-name": { + "version": "1.1.4", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/concat-map": { + "version": "0.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/emoji-regex": { + "version": "8.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-deep-equal": { + "version": "3.1.3", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/fs-extra": { + "version": "11.2.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/aws-cdk-lib/node_modules/graceful-fs": { + "version": "4.2.11", + "inBundle": true, + "license": "ISC" + }, + "node_modules/aws-cdk-lib/node_modules/ignore": { + "version": "5.3.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/aws-cdk-lib/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/json-schema-traverse": { + "version": "1.0.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/jsonfile": { + "version": "6.1.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/lodash.truncate": { + "version": "4.4.2", + "inBundle": true, + "license": "MIT" + }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/minimatch": { + "version": "3.1.2", + "inBundle": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/aws-cdk-lib/node_modules/punycode": { + "version": "2.3.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/aws-cdk-lib/node_modules/require-from-string": { + "version": "2.0.2", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/semver": { + "version": "7.6.3", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aws-cdk-lib/node_modules/slice-ansi": { + "version": "4.0.0", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/aws-cdk-lib/node_modules/string-width": { + "version": "4.2.3", + "inBundle": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/strip-ansi": { + "version": "6.0.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/aws-cdk-lib/node_modules/table": { + "version": "6.8.2", + "inBundle": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/universalify": { + "version": "2.0.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/uri-js": { + "version": "4.4.1", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/aws-cdk-lib/node_modules/yaml": { + "version": "1.10.2", + "inBundle": true, + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "dependencies": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "dependencies": { + "fast-json-stable-stringify": "2.x" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/cargo-lambda-cdk": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.22.tgz", + "integrity": "sha512-lpRh4TFR03FSWw/Ji6D3ZD18mAJjeWvU8ST5UdS6RwbdSXQT/0TbEjG/ccMCpqNGtrlx+txQ8WYURgtNbnDKcw==", + "bundleDependencies": [ + "js-toml" + ], + "dependencies": { + "js-toml": "^0.1.1" + }, + "peerDependencies": { + "aws-cdk-lib": "^2.63.0", + "constructs": "^10.0.5" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@babel/runtime-corejs3": { + "version": "7.23.2", + "inBundle": true, + "license": "MIT", + "dependencies": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/gast": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/types": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/@chevrotain/utils": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0" + }, + "node_modules/cargo-lambda-cdk/node_modules/chevrotain": { + "version": "10.5.0", + "inBundle": true, + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/core-js-pure": { + "version": "3.33.2", + "hasInstallScript": true, + "inBundle": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/js-toml": { + "version": "0.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "node_modules/cargo-lambda-cdk/node_modules/lodash": { + "version": "4.17.21", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regenerator-runtime": { + "version": "0.14.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/regexp-to-ast": { + "version": "0.5.0", + "inBundle": true, + "license": "MIT" + }, + "node_modules/cargo-lambda-cdk/node_modules/xregexp": { + "version": "5.1.1", + "inBundle": true, + "license": "MIT", + "dependencies": { + "@babel/runtime-corejs3": "^7.16.5" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==", + "engines": { + "node": ">= 16.14.0" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "peerDependencies": { + "babel-plugin-macros": "^3.1.0" + }, + "peerDependenciesMeta": { + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.27", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", + "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", + "dev": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "dependencies": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "node_modules/is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "dependencies": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "dependencies": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "dependencies": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "dependencies": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "dependencies": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner/node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "dependencies": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-locate/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ] + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "dependencies": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0", + "@jest/types": "^29.0.0", + "babel-jest": "^29.0.0", + "jest": "^29.0.0", + "typescript": ">=4.3 <6" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/ts-jest/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@aws-cdk/asset-awscli-v1": { + "version": "2.2.203", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", + "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" + }, + "@aws-cdk/asset-kubectl-v20": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", + "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + }, + "@aws-cdk/asset-node-proxy-agent-v6": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "@aws-cdk/cloud-assembly-schema": { + "version": "36.3.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", + "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "requires": { + "jsonschema": "^1.4.1", + "semver": "^7.6.3" + }, + "dependencies": { + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "semver": { + "version": "7.6.3", + "bundled": true + } + } + }, + "@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "requires": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + } + }, + "@babel/compat-data": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", + "dev": true + }, + "@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "requires": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + } + }, + "@babel/generator": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + } + }, + "@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true + }, + "@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + } + }, + "@babel/helper-string-parser": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", + "dev": true + }, + "@babel/helper-validator-identifier": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true + }, + "@babel/helpers": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", + "dev": true, + "requires": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" + } + }, + "@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dev": true, + "requires": { + "@babel/types": "^7.25.6" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-import-attributes": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.25.6.tgz", + "integrity": "sha512-sXaDXaJN9SNLymBdlWFA+bjzBhFD617ZaFiY13dGt7TVslVvVgA6fkZOP7Ki3IGElC45lwHdOTrCtKZGVAWeLQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.8" + } + }, + "@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.7" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.14.5" + } + }, + "@babel/plugin-syntax-typescript": { + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz", + "integrity": "sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.24.8" + } + }, + "@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + } + }, + "@babel/traverse": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", + "debug": "^4.3.1", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", + "dev": true, + "requires": { + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "to-fast-properties": "^2.0.0" + } + }, + "@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "dependencies": { + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + } + } + }, + "@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + } + }, + "@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true + }, + "@jest/console": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/core": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + } + }, + "@jest/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "dev": true, + "requires": { + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" + } + }, + "@jest/expect-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3" + } + }, + "@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "@jest/globals": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/types": "^29.6.3", + "jest-mock": "^29.7.0" + } + }, + "@jest/reporters": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "dev": true, + "requires": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + } + }, + "@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "requires": { + "@sinclair/typebox": "^0.27.8" + } + }, + "@jest/source-map": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", + "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + } + }, + "@jest/test-result": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "slash": "^3.0.0" + } + }, + "@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + } + }, + "@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + } + }, + "@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true + }, + "@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + } + }, + "@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, + "@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "requires": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.20.6", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.6.tgz", + "integrity": "sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==", + "dev": true, + "requires": { + "@babel/types": "^7.20.7" + } + }, + "@types/graceful-fs": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", + "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", + "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz", + "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz", + "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==", + "dev": true, + "requires": { + "@types/istanbul-lib-report": "*" + } + }, + "@types/jest": { + "version": "29.5.13", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.13.tgz", + "integrity": "sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==", + "dev": true, + "requires": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, + "@types/node": { + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", + "dev": true, + "requires": { + "undici-types": "~5.26.4" + } + }, + "@types/stack-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", + "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", + "dev": true + }, + "@types/yargs": { + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "21.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", + "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", + "dev": true + }, + "acorn": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "dev": true + }, + "acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "aws-cdk": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk/-/aws-cdk-2.159.1.tgz", + "integrity": "sha512-bkJOxic/NpJYQCF3MQhfyJVlFtIzMJeVGZp9jZa7TczxJp79Q/TNKzVJYv6GFabNS1wglGPfWkFB/rIJlRhJkg==", + "dev": true, + "requires": { + "fsevents": "2.3.2" + } + }, + "aws-cdk-lib": { + "version": "2.159.1", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", + "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "requires": { + "@aws-cdk/asset-awscli-v1": "^2.2.202", + "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@balena/dockerignore": "^1.0.2", + "case": "1.6.3", + "fs-extra": "^11.2.0", + "ignore": "^5.3.2", + "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", + "minimatch": "^3.1.2", + "punycode": "^2.3.1", + "semver": "^7.6.3", + "table": "^6.8.2", + "yaml": "1.10.2" + }, + "dependencies": { + "@balena/dockerignore": { + "version": "1.0.2", + "bundled": true + }, + "ajv": { + "version": "8.17.1", + "bundled": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "bundled": true + }, + "ansi-styles": { + "version": "4.3.0", + "bundled": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "astral-regex": { + "version": "2.0.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.2", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "case": { + "version": "1.6.3", + "bundled": true + }, + "color-convert": { + "version": "2.0.1", + "bundled": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "emoji-regex": { + "version": "8.0.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "bundled": true + }, + "fast-uri": { + "version": "3.0.1", + "bundled": true + }, + "fs-extra": { + "version": "11.2.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.2.11", + "bundled": true + }, + "ignore": { + "version": "5.3.2", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "bundled": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "bundled": true + }, + "jsonfile": { + "version": "6.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "lodash.truncate": { + "version": "4.4.2", + "bundled": true + }, + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", + "bundled": true, + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "punycode": { + "version": "2.3.1", + "bundled": true + }, + "require-from-string": { + "version": "2.0.2", + "bundled": true + }, + "semver": { + "version": "7.6.3", + "bundled": true + }, + "slice-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "bundled": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "table": { + "version": "6.8.2", + "bundled": true, + "requires": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + } + }, + "universalify": { + "version": "2.0.1", + "bundled": true + }, + "uri-js": { + "version": "4.4.1", + "requires": { + "punycode": "^2.1.0" + } + }, + "yaml": { + "version": "1.10.2", + "bundled": true + } + } + }, + "babel-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "dev": true, + "requires": { + "@jest/transform": "^29.7.0", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.6.3", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + } + }, + "babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "dependencies": { + "istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "requires": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + } + } + } + }, + "babel-plugin-jest-hoist": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", + "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", + "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5" + } + }, + "babel-preset-jest": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", + "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "requires": { + "fill-range": "^7.1.1" + } + }, + "browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + } + }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001662", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001662.tgz", + "integrity": "sha512-sgMUVwLmGseH8ZIrm1d51UbrhqMCH3jvS7gF/M6byuHOnKyLOBL7W8yz5V02OHwgLGA36o/AFhWzzh4uc5aqTA==", + "dev": true + }, + "cargo-lambda-cdk": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/cargo-lambda-cdk/-/cargo-lambda-cdk-0.0.22.tgz", + "integrity": "sha512-lpRh4TFR03FSWw/Ji6D3ZD18mAJjeWvU8ST5UdS6RwbdSXQT/0TbEjG/ccMCpqNGtrlx+txQ8WYURgtNbnDKcw==", + "requires": { + "js-toml": "^0.1.1" + }, + "dependencies": { + "@babel/runtime-corejs3": { + "version": "7.23.2", + "bundled": true, + "requires": { + "core-js-pure": "^3.30.2", + "regenerator-runtime": "^0.14.0" + } + }, + "@chevrotain/cst-dts-gen": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/gast": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/types": "10.5.0", + "lodash": "4.17.21" + } + }, + "@chevrotain/types": { + "version": "10.5.0", + "bundled": true + }, + "@chevrotain/utils": { + "version": "10.5.0", + "bundled": true + }, + "chevrotain": { + "version": "10.5.0", + "bundled": true, + "requires": { + "@chevrotain/cst-dts-gen": "10.5.0", + "@chevrotain/gast": "10.5.0", + "@chevrotain/types": "10.5.0", + "@chevrotain/utils": "10.5.0", + "lodash": "4.17.21", + "regexp-to-ast": "0.5.0" + } + }, + "core-js-pure": { + "version": "3.33.2", + "bundled": true + }, + "js-toml": { + "version": "0.1.1", + "bundled": true, + "requires": { + "chevrotain": "^10.4.1", + "xregexp": "^5.1.1" + } + }, + "lodash": { + "version": "4.17.21", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.14.0", + "bundled": true + }, + "regexp-to-ast": { + "version": "0.5.0", + "bundled": true + }, + "xregexp": { + "version": "5.1.1", + "bundled": true, + "requires": { + "@babel/runtime-corejs3": "^7.16.5" + } + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true + }, + "ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "dev": true + }, + "cjs-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz", + "integrity": "sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==", + "dev": true + }, + "cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true + }, + "collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constructs": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/constructs/-/constructs-10.3.0.tgz", + "integrity": "sha512-vbK8i3rIb/xwZxSpTjz3SagHn1qq9BChLEfy5Hf6fB3/2eFbrwt2n9kHwQcS0CPTRBesreeAcsJfMq2229FnbQ==" + }, + "convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + } + }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "dedent": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", + "integrity": "sha512-NHQtfOOW68WD8lgypbLA5oT+Bt0xXJhiYvoR6SmmNXZfpzOGXwdKWmcwG8N7PwVVWV3eF/68nmD9BaJSsTBhyQ==", + "dev": true, + "requires": {} + }, + "deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true + }, + "detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true + }, + "ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "requires": { + "jake": "^10.8.5" + } + }, + "electron-to-chromium": { + "version": "1.5.27", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.27.tgz", + "integrity": "sha512-o37j1vZqCoEgBuWWXLHQgTN/KDKe7zwpiY5CPeq2RvUqOyJw9xnrULzZAEVQ5p4h+zjMk7hgtOoPdnLxr7m/jw==", + "dev": true + }, + "emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true + }, + "escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true + }, + "expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "dev": true, + "requires": { + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "requires": { + "bser": "2.1.1" + } + }, + "filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "requires": { + "minimatch": "^5.0.1" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "import-local": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", + "integrity": "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, + "is-core-module": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "requires": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + } + }, + "istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + } + }, + "istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + } + }, + "jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "requires": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + } + }, + "jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/types": "^29.6.3", + "import-local": "^3.0.2", + "jest-cli": "^29.7.0" + } + }, + "jest-changed-files": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "dev": true, + "requires": { + "execa": "^5.0.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0" + } + }, + "jest-circus": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^1.0.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "p-limit": "^3.1.0", + "pretty-format": "^29.7.0", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-cli": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "dev": true, + "requires": { + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "create-jest": "^29.7.0", + "exit": "^0.1.2", + "import-local": "^3.0.2", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "yargs": "^17.3.1" + } + }, + "jest-config": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-jest": "^29.7.0", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + } + }, + "jest-diff": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-docblock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "dev": true, + "requires": { + "detect-newline": "^3.0.0" + } + }, + "jest-each": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" + } + }, + "jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + } + }, + "jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true + }, + "jest-haste-map": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.3.2", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + } + }, + "jest-leak-detector": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "dev": true, + "requires": { + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-matcher-utils": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + } + }, + "jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + } + }, + "jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "requires": {} + }, + "jest-regex-util": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", + "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "dev": true + }, + "jest-resolve": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + } + }, + "jest-resolve-dependencies": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "dev": true, + "requires": { + "jest-regex-util": "^29.6.3", + "jest-snapshot": "^29.7.0" + } + }, + "jest-runner": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "dev": true, + "requires": { + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "dependencies": { + "source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "dev": true, + "requires": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", + "@jest/source-map": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + } + }, + "jest-snapshot": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "dev": true, + "requires": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", + "@jest/types": "^29.6.3", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.7.0", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "natural-compare": "^1.4.0", + "pretty-format": "^29.7.0", + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + } + }, + "jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "requires": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "dependencies": { + "camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true + } + } + }, + "jest-watcher": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "dev": true, + "requires": { + "@jest/test-result": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.7.0", + "string-length": "^4.0.1" + } + }, + "jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "requires": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "requires": { + "semver": "^7.5.3" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "requires": { + "tmpl": "1.0.5" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "requires": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true + }, + "node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.0.tgz", + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "requires": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true + } + } + }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, + "pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "dev": true + }, + "react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dev": true, + "requires": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true + }, + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "requires": { + "escape-string-regexp": "^2.0.0" + } + }, + "string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "requires": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "requires": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + } + }, + "tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-jest": { + "version": "29.2.5", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", + "integrity": "sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA==", + "dev": true, + "requires": { + "bs-logger": "^0.2.6", + "ejs": "^3.1.10", + "fast-json-stable-stringify": "^2.1.0", + "jest-util": "^29.0.0", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.6.3", + "yargs-parser": "^21.1.1" + }, + "dependencies": { + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + } + } + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true + }, + "typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "dev": true + }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "dev": true + }, + "update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "requires": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + } + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "v8-to-istanbul": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", + "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^2.0.0" + } + }, + "walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "requires": { + "makeerror": "1.0.12" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "requires": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/examples/advanced-appconfig-feature-flags/cdk/package.json b/examples/advanced-appconfig-feature-flags/cdk/package.json new file mode 100644 index 00000000..83cb9552 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/package.json @@ -0,0 +1,28 @@ +{ + "name": "cdk", + "version": "0.1.0", + "bin": { + "cdk": "bin/cdk.js" + }, + "scripts": { + "build": "tsc", + "watch": "tsc -w", + "test": "jest", + "cdk": "cdk" + }, + "devDependencies": { + "@types/jest": "^29.5.12", + "@types/node": "20.14.2", + "aws-cdk": "2.159.1", + "jest": "^29.7.0", + "ts-jest": "^29.1.4", + "ts-node": "^10.9.2", + "typescript": "~5.4.5" + }, + "dependencies": { + "aws-cdk-lib": "2.159.1", + "cargo-lambda-cdk": "^0.0.22", + "constructs": "^10.0.0", + "source-map-support": "^0.5.21" + } +} \ No newline at end of file diff --git a/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts b/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts new file mode 100644 index 00000000..1e6b29c8 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/test/cdk.test.ts @@ -0,0 +1,17 @@ +// import * as cdk from 'aws-cdk-lib'; +// import { Template } from 'aws-cdk-lib/assertions'; +// import * as Cdk from '../lib/cdk-stack'; + +// example test. To run these tests, uncomment this file along with the +// example resource in lib/cdk-stack.ts +test('SQS Queue Created', () => { +// const app = new cdk.App(); +// // WHEN +// const stack = new Cdk.CdkStack(app, 'MyTestStack'); +// // THEN +// const template = Template.fromStack(stack); + +// template.hasResourceProperties('AWS::SQS::Queue', { +// VisibilityTimeout: 300 +// }); +}); diff --git a/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json b/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json new file mode 100644 index 00000000..aaa7dc51 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/cdk/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": [ + "es2020", + "dom" + ], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "typeRoots": [ + "./node_modules/@types" + ] + }, + "exclude": [ + "node_modules", + "cdk.out" + ] +} diff --git a/examples/advanced-appconfig-feature-flags/src/appconfig.rs b/examples/advanced-appconfig-feature-flags/src/appconfig.rs new file mode 100644 index 00000000..9dba6610 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/src/appconfig.rs @@ -0,0 +1,88 @@ +//! # Rust AppConfig Client +//! +//! This library provides a Rust client for interacting with the AWS AppConfig local extension for AWS Lambda and ECS. +//! It allows you to retrieve configuration data for your application based on the application ID, environment ID, +//! and configuration profile ID. +//! +//! ## Features +//! +//! - Simple API for retrieving configuration data +//! - Asynchronous operations using `tokio` and `reqwest` +//! - Error handling with custom `AppConfigError` type +//! - Deserialization of configuration data into user-defined types +//! +//! ## Usage +//! +//! ```rust +//! use appconfig::{AppConfigClient, ConfigurationFetcher, AppConfigError}; +//! +//! #[tokio::main] +//! async fn main() -> Result<(), AppConfigError> { +//! let client = AppConfigClient::new("app_id", "env_id", "profile_id", 2772); +//! +//! let config: YourConfigType = client.get_configuration().await?; +//! +//! println!("Received config: {:?}", config); +//! +//! Ok(()) +//! } +//! ``` +use async_trait::async_trait; +use reqwest::Client; +use serde::de::DeserializeOwned; +use thiserror::Error; + +#[derive(Clone)] +pub struct AppConfigClient { + client: Client, + application_id: String, + environment_id: String, + configuration_profile_id: String, + port: u16, +} + +impl AppConfigClient { + pub fn new(application_id: &str, environment_id: &str, configuration_profile_id: &str, port: u16) -> Self { + AppConfigClient { + client: Client::new(), + application_id: application_id.to_string(), + environment_id: environment_id.to_string(), + configuration_profile_id: configuration_profile_id.to_string(), + port, + } + } +} + +#[async_trait] +impl ConfigurationFetcher for AppConfigClient { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send, + { + let url = format!( + "http://localhost:{}/applications/{}/environments/{}/configurations/{}", + self.port, self.application_id, self.environment_id, self.configuration_profile_id + ); + + let response = self.client.get(&url).send().await?; + let config: T = response.json().await?; + + Ok(config) + } +} + +#[derive(Error, Debug)] +pub enum AppConfigError { + #[error("Failed to send request: {0}")] + RequestError(#[from] reqwest::Error), + + #[error("Failed to parse JSON: {0}")] + JsonParseError(#[from] serde_json::Error), +} + +#[async_trait] +pub trait ConfigurationFetcher { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send; +} diff --git a/examples/advanced-appconfig-feature-flags/src/main.rs b/examples/advanced-appconfig-feature-flags/src/main.rs new file mode 100644 index 00000000..b7d5e515 --- /dev/null +++ b/examples/advanced-appconfig-feature-flags/src/main.rs @@ -0,0 +1,126 @@ +use lambda_runtime::{run, service_fn, tracing, Error, LambdaEvent}; +use serde::{Deserialize, Serialize}; +use std::env; + +mod appconfig; +use crate::appconfig::{AppConfigClient, ConfigurationFetcher}; + +#[derive(Deserialize)] +struct Request { + quote: String, +} + +#[derive(Serialize)] +struct Response { + req_id: String, + msg: String, +} + +#[derive(Deserialize)] +struct AppConfig { + #[serde(rename = "spanish-response")] + spanish_response: bool, + // Add other fields as needed +} + +async fn function_handler( + event: LambdaEvent, + config_fetcher: &T, +) -> Result { + // Extract some useful info from the request + let quote = event.payload.quote; + + // Send a GET request to the local AppConfig endpoint + let config: AppConfig = config_fetcher.get_configuration().await?; + + // Use the feature flag + let msg = if config.spanish_response { + format!("{}, in spanish.", quote) + } else { + format!("{}.", quote) + }; + + // Return `Response` (it will be serialized to JSON automatically by the runtime) + Ok(Response { + req_id: event.context.request_id, + msg, + }) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + // Extract the AppConfig port from the environment + let app_config_port = env::var("AWS_APPCONFIG_EXTENSION_HTTP_PORT") + .unwrap_or_else(|_| "2772".to_string()) + .parse::() + .expect("Invalid port number for AWS_APPCONFIG_EXTENSION_HTTP_PORT"); + + // Create a new AppConfigClient with the extracted port + let app_config_client = AppConfigClient::new( + &env::var("APPLICATION_ID").expect("APPLICATION_ID must be set"), + &env::var("ENVIRONMENT_ID").expect("ENVIRONMENT_ID must be set"), + &env::var("CONFIGURATION_PROFILE_ID").expect("CONFIGURATION_PROFILE_ID must be set"), + app_config_port, + ); + + // Use a closure to capture app_config_client and pass it to function_handler + run(service_fn(|event| function_handler(event, &app_config_client))).await +} + +#[cfg(test)] +mod tests { + use super::*; + use async_trait::async_trait; + use lambda_runtime::Context; + use serde::de::DeserializeOwned; + + struct MockConfigFetcher { + spanish_response: bool, + } + + #[async_trait] + impl ConfigurationFetcher for MockConfigFetcher { + async fn get_configuration(&self) -> Result + where + T: DeserializeOwned + Send, + { + let value = serde_json::json!({ + "spanish-response": self.spanish_response + }); + let value: T = serde_json::from_value(value)?; + Ok(value) + } + } + + #[tokio::test] + async fn test_function_handler_english() { + let mock_fetcher = MockConfigFetcher { + spanish_response: false, + }; + let request = Request { + quote: "Hello, world".to_string(), + }; + let context = Context::default(); + let event = LambdaEvent::new(request, context); + + let result = function_handler(event, &mock_fetcher).await.unwrap(); + + assert_eq!(result.msg, "Hello, world."); + } + + #[tokio::test] + async fn test_function_handler_spanish() { + let mock_fetcher = MockConfigFetcher { spanish_response: true }; + let request = Request { + quote: "Hello, world".to_string(), + }; + let context = Context::default(); + let event = LambdaEvent::new(request, context); + + let result = function_handler(event, &mock_fetcher).await.unwrap(); + + assert_eq!(result.msg, "Hello, world, in spanish."); + } +} From 1c6343983ad030396dd84cbac45a35629ff1b907 Mon Sep 17 00:00:00 2001 From: George Hahn Date: Thu, 3 Oct 2024 12:57:00 -0500 Subject: [PATCH 157/211] Update to tower 0.5 (#929) * Update to tower 0.5 * Switch to `tower` from workspace * Update tower version in example --- Cargo.toml | 2 +- examples/opentelemetry-tracing/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c55f0e60..867e9c0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,6 @@ http-serde = "2.0" hyper = "1.0" hyper-util = "0.1.1" pin-project-lite = "0.2" -tower = "0.4" +tower = "0.5" tower-layer = "0.3" tower-service = "0.3" diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index c80b997d..98108d39 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -12,7 +12,7 @@ opentelemetry-stdout = { version = "0.3", features = ["trace"] } pin-project = "1" serde_json = "1.0" tokio = "1" -tower = "0.4" +tower = "0.5" tracing = "0.1" tracing-opentelemetry = "0.23" tracing-subscriber = "0.3" diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index c1afd732..16b6dace 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -35,5 +35,5 @@ tokio = { version = "1.0", features = [ "rt-multi-thread", ] } tokio-stream = "0.1.2" -tower = { version = "0.4", features = ["make", "util"] } +tower = { workspace = true, features = ["make", "util"] } tracing = { version = "0.1", features = ["log"] } From 19c190d6762ea755cb14462b6c0347afb4352a29 Mon Sep 17 00:00:00 2001 From: Raees Iqbal <10067728+RaeesBhatti@users.noreply.github.com> Date: Thu, 17 Oct 2024 20:40:54 -0400 Subject: [PATCH 158/211] Only add URL query if it's not empty (#933) --- lambda-http/src/request.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index afe233eb..a9281b46 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -129,8 +129,10 @@ fn into_api_gateway_v2_request(ag: ApiGatewayV2httpRequest) -> http::Request Date: Mon, 18 Nov 2024 20:20:55 +0800 Subject: [PATCH 159/211] Release events 0.16.0 and http 0.14.0 (#938) --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index d9774104..b8d34055 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.15.1" +version = "0.16.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 08b70d4e..46198dca 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.13.0" +version = "0.14.0" authors = [ "David Calavera ", "Harold Sun ", @@ -50,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.15.0" +version = "0.16.0" default-features = false features = ["alb", "apigw"] From 16799d8501ecc69792528d568ace39ddfa674cb5 Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 18 Nov 2024 13:10:30 +0000 Subject: [PATCH 160/211] feat: add dependabot (#941) --- .github/dependabot.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..24a9fc5b --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,30 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "." + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-events" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-extension" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-http" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-integration-tests" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-runtime-api-client" + schedule: + interval: "weekly" + - package-ecosystem: "cargo" + directory: "lambda-runtime" + schedule: + interval: "weekly" \ No newline at end of file From a1f8c05dcc0333dca83661a705cf8f04215de719 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 09:33:30 +0000 Subject: [PATCH 161/211] Update opentelemetry-semantic-conventions requirement from 0.14 to 0.27 (#942) * Update opentelemetry-semantic-conventions requirement from 0.14 to 0.27 Updates the requirements on [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-rust) to permit the latest version. - [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases) - [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-semantic-conventions-0.14.0...opentelemetry-semantic-conventions-0.27.0) --- updated-dependencies: - dependency-name: opentelemetry-semantic-conventions dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: add semconv_experimental flag (#943) --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maxime David --- lambda-runtime/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 0f63cd47..b4a7ad3d 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -41,7 +41,7 @@ hyper-util = { workspace = true, features = [ ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.14", optional = true } +opentelemetry-semantic-conventions = { version = "0.27", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" From f024fd0c3b87791adc7b98c7fd0ed020f05ebb3a Mon Sep 17 00:00:00 2001 From: Maxime David Date: Mon, 9 Dec 2024 11:12:11 +0000 Subject: [PATCH 162/211] fix: clippy error + bump MSRV (#948) * test-ci * fix: bump MSRV * fix: fix GitHub Actions trigger path * fix: clippy errors * fix: cleanup --- .github/workflows/build-events.yml | 4 +++- .github/workflows/build-extension.yml | 6 +++++- .github/workflows/build-runtime.yml | 4 +++- lambda-events/src/custom_serde/codebuild_time.rs | 2 +- lambda-events/src/custom_serde/float_unix_epoch.rs | 2 +- lambda-events/src/custom_serde/http_method.rs | 2 +- lambda-events/src/encodings/http.rs | 2 +- lambda-events/src/event/cloudwatch_alarms/mod.rs | 2 +- lambda-events/src/event/documentdb/events/insert_event.rs | 1 - lambda-events/src/event/s3/object_lambda.rs | 1 - lambda-extension/src/extension.rs | 6 +++--- lambda-http/src/lib.rs | 2 +- lambda-runtime/src/layers/panic.rs | 6 +++--- lambda-runtime/src/requests.rs | 4 ++-- lambda-runtime/src/runtime.rs | 4 ++-- 15 files changed, 27 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 3026f1ac..6894e331 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -4,9 +4,11 @@ on: push: paths: - "lambda-events/**" + - "Cargo.toml" pull_request: paths: - "lambda-events/**" + - "Cargo.toml" jobs: build: @@ -14,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index d09b08c0..cb23c289 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -5,11 +5,15 @@ on: paths: - 'lambda-runtime-api-client/**' - 'lambda-extension/**' + - 'lambda-runtime/**' + - 'Cargo.toml' pull_request: paths: - 'lambda-runtime-api-client/**' - 'lambda-extension/**' + - 'lambda-runtime/**' + - 'Cargo.toml' jobs: @@ -18,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index a52927b5..dfc59ee8 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -6,12 +6,14 @@ on: - 'lambda-runtime-api-client/**' - 'lambda-runtime/**' - 'lambda-http/**' + - 'Cargo.toml' pull_request: paths: - 'lambda-runtime-api-client/**' - 'lambda-runtime/**' - 'lambda-http/**' + - 'Cargo.toml' jobs: build-runtime: @@ -19,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.70.0" # Current MSRV + - "1.71.1" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 07bd0a5c..92b0f796 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -10,7 +10,7 @@ use std::fmt; const CODEBUILD_TIME_FORMAT: &str = "%b %e, %Y %l:%M:%S %p"; struct TimeVisitor; -impl<'de> Visitor<'de> for TimeVisitor { +impl Visitor<'_> for TimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 437e2b1c..805c672f 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -73,7 +73,7 @@ where d.deserialize_f64(SecondsFloatTimestampVisitor) } -impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { +impl de::Visitor<'_> for SecondsFloatTimestampVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/custom_serde/http_method.rs b/lambda-events/src/custom_serde/http_method.rs index 42a94dd7..1332c777 100644 --- a/lambda-events/src/custom_serde/http_method.rs +++ b/lambda-events/src/custom_serde/http_method.rs @@ -10,7 +10,7 @@ pub fn serialize(method: &Method, ser: S) -> Result Visitor<'de> for MethodVisitor { +impl Visitor<'_> for MethodVisitor { type Value = Method; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 56cce76a..d978f522 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -197,7 +197,7 @@ impl<'de> Deserialize<'de> for Body { { struct BodyVisitor; - impl<'de> Visitor<'de> for BodyVisitor { + impl Visitor<'_> for BodyVisitor { type Value = Body; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 01174566..d99f3c94 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -232,7 +232,7 @@ impl Serialize for CloudWatchAlarmStateReasonData { struct ReasonDataVisitor; -impl<'de> Visitor<'de> for ReasonDataVisitor { +impl Visitor<'_> for ReasonDataVisitor { type Value = CloudWatchAlarmStateReasonData; fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index de9b5843..2f4df397 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -4,7 +4,6 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentK #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] - pub struct ChangeInsertEvent { #[serde(rename = "_id")] id: DocumentId, diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 2abcc797..738cd72c 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -87,7 +87,6 @@ pub struct UserRequest { /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] - pub struct UserIdentity { pub r#type: String, pub principal_id: String, diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index d9717243..15e0befd 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -43,7 +43,7 @@ pub struct Extension<'a, E, L, T> { telemetry_port_number: u16, } -impl<'a> Extension<'a, Identity, MakeIdentity>, MakeIdentity>> { +impl Extension<'_, Identity, MakeIdentity>, MakeIdentity>> { /// Create a new base [`Extension`] with a no-op events processor pub fn new() -> Self { Extension { @@ -62,8 +62,8 @@ impl<'a> Extension<'a, Identity, MakeIdentity>, Make } } -impl<'a> Default - for Extension<'a, Identity, MakeIdentity>, MakeIdentity>> +impl Default + for Extension<'_, Identity, MakeIdentity>, MakeIdentity>> { fn default() -> Self { Self::new() diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 233d6992..92cd5dae 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -114,7 +114,7 @@ pub enum TransformResponse<'a, R, E> { Response(RequestOrigin, ResponseFuture), } -impl<'a, R, E> Future for TransformResponse<'a, R, E> +impl Future for TransformResponse<'_, R, E> where R: IntoResponse, { diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index c76348ac..4b92e3c8 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -18,7 +18,7 @@ pub struct CatchPanicService<'a, S> { _phantom: PhantomData<&'a ()>, } -impl<'a, S> CatchPanicService<'a, S> { +impl CatchPanicService<'_, S> { pub fn new(inner: S) -> Self { Self { inner, @@ -66,7 +66,7 @@ pub enum CatchPanicFuture<'a, F> { Error(Box), } -impl<'a, F, T, E> Future for CatchPanicFuture<'a, F> +impl Future for CatchPanicFuture<'_, F> where F: Future>, E: Into + Debug, @@ -95,7 +95,7 @@ where } } -impl<'a, F> CatchPanicFuture<'a, F> { +impl CatchPanicFuture<'_, F> { fn build_panic_diagnostic(err: &Box) -> Diagnostic { let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index 729272f2..e8b0183c 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -72,7 +72,7 @@ where } } -impl<'a, R, B, S, D, E> IntoRequest for EventCompletionRequest<'a, R, B, S, D, E> +impl IntoRequest for EventCompletionRequest<'_, R, B, S, D, E> where R: IntoFunctionResponse, B: Serialize, @@ -170,7 +170,7 @@ impl<'a> EventErrorRequest<'a> { } } -impl<'a> IntoRequest for EventErrorRequest<'a> { +impl IntoRequest for EventErrorRequest<'_> { fn into_req(self) -> Result, Error> { let uri = format!("/2018-06-01/runtime/invocation/{}/error", self.request_id); let uri = Uri::from_str(&uri)?; diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 1c676480..5749fbb7 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -55,11 +55,11 @@ pub struct Runtime { client: Arc, } -impl<'a, F, EventPayload, Response, BufferedResponse, StreamingResponse, StreamItem, StreamError> +impl Runtime< RuntimeApiClientService< RuntimeApiResponseService< - CatchPanicService<'a, F>, + CatchPanicService<'_, F>, EventPayload, Response, BufferedResponse, From 9416d726ff98cb0bc02ee328c4b1c246d2f36679 Mon Sep 17 00:00:00 2001 From: Alessandro Bologna Date: Thu, 12 Dec 2024 05:28:44 -0500 Subject: [PATCH 163/211] Feature/otel span kind support (#946) * feat(otel): add span kind support to Lambda invocation tracing * fix fmt * docs(example): demonstrate OpenTelemetry span kind usage * fix fmt --- examples/opentelemetry-tracing/src/main.rs | 3 +++ lambda-runtime/src/layers/otel.rs | 3 ++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 85c3791c..062f5a11 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -1,5 +1,6 @@ use lambda_runtime::{ layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + tracing::Span, LambdaEvent, Runtime, }; use opentelemetry::trace::TracerProvider; @@ -8,6 +9,8 @@ use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; async fn echo(event: LambdaEvent) -> Result { + let span = Span::current(); + span.record("otel.kind", "SERVER"); Ok(event.payload) } diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index e6b7cfff..f50f36f7 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -4,7 +4,7 @@ use crate::LambdaInvocation; use opentelemetry_semantic_conventions::trace as traceconv; use pin_project::pin_project; use tower::{Layer, Service}; -use tracing::{instrument::Instrumented, Instrument}; +use tracing::{field, instrument::Instrumented, Instrument}; /// Tower layer to add OpenTelemetry tracing to a Lambda function invocation. The layer accepts /// a function to flush OpenTelemetry after the end of the invocation. @@ -75,6 +75,7 @@ where let span = tracing::info_span!( "Lambda function invocation", "otel.name" = req.context.env_config.function_name, + "otel.kind" = field::Empty, { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, { traceconv::FAAS_COLDSTART } = self.coldstart From f69280e9b92e9a7acc92cfa04a860d9b2b83b5c0 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Tue, 17 Dec 2024 06:00:55 +0800 Subject: [PATCH 164/211] Update MSRV to 1.71.1 (#949) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31ba399b..1a93d85c 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.70, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.71.1, and is not guaranteed to build on compiler versions earlier than that. ## Security From 2ee91c0fc0785fc9b7fe8ffc91df129f2c32178a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 12:48:50 +0100 Subject: [PATCH 165/211] Bump aws-cdk-lib from 2.126.0 to 2.186.0 in /examples/http-axum/cdk (#976) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.126.0 to 2.186.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.186.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.186.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 258 ++++++++++++++--------- examples/http-axum/cdk/package.json | 2 +- 2 files changed, 156 insertions(+), 104 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index 9a7e3026..e572dacf 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "^2.126.0", + "aws-cdk-lib": "^2.186.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -40,19 +40,52 @@ } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.230", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", + "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==", + "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", - "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==", + "license": "Apache-2.0" + }, + "node_modules/@aws-cdk/cloud-assembly-schema": { + "version": "40.7.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", + "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "bundleDependencies": [ + "jsonschema", + "semver" + ], + "license": "Apache-2.0", + "dependencies": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "engines": { + "node": ">= 14.15.0" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { + "version": "1.4.1", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { + "version": "7.7.1", + "inBundle": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, "node_modules/@babel/code-frame": { "version": "7.23.5", @@ -1315,9 +1348,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.126.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", - "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "version": "2.186.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", + "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1328,21 +1361,24 @@ "punycode", "semver", "table", - "yaml" + "yaml", + "mime-types" ], + "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^40.7.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.0", - "jsonschema": "^1.4.1", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.5.4", - "table": "^6.8.1", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "engines": { @@ -1358,14 +1394,14 @@ "license": "Apache-2.0" }, "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.12.0", + "version": "8.17.1", "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" }, "funding": { "type": "github", @@ -1455,8 +1491,23 @@ "inBundle": true, "license": "MIT" }, + "node_modules/aws-cdk-lib/node_modules/fast-uri": { + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "inBundle": true, + "license": "BSD-3-Clause" + }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -1474,7 +1525,7 @@ "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.2", "inBundle": true, "license": "MIT", "engines": { @@ -1506,7 +1557,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "inBundle": true, "license": "MIT", "engines": { @@ -1518,15 +1569,23 @@ "inBundle": true, "license": "MIT" }, - "node_modules/aws-cdk-lib/node_modules/lru-cache": { - "version": "6.0.0", + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", "inBundle": true, - "license": "ISC", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "inBundle": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" }, "engines": { - "node": ">=10" + "node": ">= 0.6" } }, "node_modules/aws-cdk-lib/node_modules/minimatch": { @@ -1557,12 +1616,9 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.5.4", + "version": "7.7.1", "inBundle": true, "license": "ISC", - "dependencies": { - "lru-cache": "^6.0.0" - }, "bin": { "semver": "bin/semver.js" }, @@ -1611,7 +1667,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.1", + "version": "6.9.0", "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -1633,19 +1689,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-cdk-lib/node_modules/uri-js": { - "version": "4.4.1", - "inBundle": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/aws-cdk-lib/node_modules/yallist": { - "version": "4.0.0", - "inBundle": true, - "license": "ISC" - }, "node_modules/aws-cdk-lib/node_modules/yaml": { "version": "1.10.2", "inBundle": true, @@ -4408,19 +4451,33 @@ } }, "@aws-cdk/asset-awscli-v1": { - "version": "2.2.202", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.202.tgz", - "integrity": "sha512-JqlF0D4+EVugnG5dAsNZMqhu3HW7ehOXm5SDMxMbXNDMdsF0pxtQKNHRl52z1U9igsHmaFpUgSGjbhAJ+0JONg==" - }, - "@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.230", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.230.tgz", + "integrity": "sha512-kUnhKIYu42hqBa6a8x2/7o29ObpJgjYGQy28lZDq9awXyvpR62I2bRxrNKNR3uFUQz3ySuT9JXhGHhuZPdbnFw==" }, "@aws-cdk/asset-node-proxy-agent-v6": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.0.1.tgz", - "integrity": "sha512-DDt4SLdLOwWCjGtltH4VCST7hpOI5DzieuhGZsBpZ+AgJdSI2GCjklCXm0GCTwJG/SolkL5dtQXyUKgg9luBDg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-node-proxy-agent-v6/-/asset-node-proxy-agent-v6-2.1.0.tgz", + "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" + }, + "@aws-cdk/cloud-assembly-schema": { + "version": "40.7.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", + "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "requires": { + "jsonschema": "~1.4.1", + "semver": "^7.7.1" + }, + "dependencies": { + "jsonschema": { + "version": "1.4.1", + "bundled": true + }, + "semver": { + "version": "7.7.1", + "bundled": true + } + } }, "@babel/code-frame": { "version": "7.23.5", @@ -5423,22 +5480,23 @@ } }, "aws-cdk-lib": { - "version": "2.126.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.126.0.tgz", - "integrity": "sha512-HlwBYiqNCfhPUyozjoV4zJIBgH0adE/zTVATmeaM1jcNGs6jwkPMpWsF75JuXOoVtkcfpBFyNKloGd82nTiB0A==", + "version": "2.186.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", + "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", + "@aws-cdk/cloud-assembly-schema": "^40.7.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", - "ignore": "^5.3.0", - "jsonschema": "^1.4.1", + "fs-extra": "^11.3.0", + "ignore": "^5.3.2", + "jsonschema": "^1.5.0", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.5.4", - "table": "^6.8.1", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "dependencies": { @@ -5447,13 +5505,13 @@ "bundled": true }, "ajv": { - "version": "8.12.0", + "version": "8.17.1", "bundled": true, "requires": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "require-from-string": "^2.0.2" } }, "ansi-regex": { @@ -5510,8 +5568,12 @@ "version": "3.1.3", "bundled": true }, + "fast-uri": { + "version": "3.0.6", + "bundled": true + }, "fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "bundled": true, "requires": { "graceful-fs": "^4.2.0", @@ -5524,7 +5586,7 @@ "bundled": true }, "ignore": { - "version": "5.3.0", + "version": "5.3.2", "bundled": true }, "is-fullwidth-code-point": { @@ -5544,18 +5606,22 @@ } }, "jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true }, "lodash.truncate": { "version": "4.4.2", "bundled": true }, - "lru-cache": { - "version": "6.0.0", + "mime-db": { + "version": "1.52.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.35", "bundled": true, "requires": { - "yallist": "^4.0.0" + "mime-db": "1.52.0" } }, "minimatch": { @@ -5574,11 +5640,8 @@ "bundled": true }, "semver": { - "version": "7.5.4", - "bundled": true, - "requires": { - "lru-cache": "^6.0.0" - } + "version": "7.7.1", + "bundled": true }, "slice-ansi": { "version": "4.0.0", @@ -5606,7 +5669,7 @@ } }, "table": { - "version": "6.8.1", + "version": "6.9.0", "bundled": true, "requires": { "ajv": "^8.0.1", @@ -5620,17 +5683,6 @@ "version": "2.0.1", "bundled": true }, - "uri-js": { - "version": "4.4.1", - "bundled": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "yallist": { - "version": "4.0.0", - "bundled": true - }, "yaml": { "version": "1.10.2", "bundled": true diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json index 9117d19f..d9e6f496 100644 --- a/examples/http-axum/cdk/package.json +++ b/examples/http-axum/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.3.3" }, "dependencies": { - "aws-cdk-lib": "^2.126.0", + "aws-cdk-lib": "^2.186.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From af605cc137df335bb97fd0325c19f3ddbefd95ba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:00:59 +0100 Subject: [PATCH 166/211] Bump aws-cdk-lib from 2.186.0 to 2.193.0 in /examples/http-axum/cdk (#979) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.186.0 to 2.193.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.193.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.193.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/http-axum/cdk/package-lock.json | 34 ++++++++++++------------ examples/http-axum/cdk/package.json | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/http-axum/cdk/package-lock.json b/examples/http-axum/cdk/package-lock.json index e572dacf..a7df2926 100644 --- a/examples/http-axum/cdk/package-lock.json +++ b/examples/http-axum/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "^2.186.0", + "aws-cdk-lib": "^2.193.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -52,9 +52,9 @@ "license": "Apache-2.0" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "40.7.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", - "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "bundleDependencies": [ "jsonschema", "semver" @@ -1348,9 +1348,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.186.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", - "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1366,9 +1366,9 @@ ], "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^40.7.0", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.0", @@ -4461,9 +4461,9 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "@aws-cdk/cloud-assembly-schema": { - "version": "40.7.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-40.7.0.tgz", - "integrity": "sha512-00wVKn9pOOGXbeNwA4E8FUFt0zIB4PmSO7PvIiDWgpaFX3G/sWyy0A3s6bg/n2Yvkghu8r4a8ckm+mAzkAYmfA==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "requires": { "jsonschema": "~1.4.1", "semver": "^7.7.1" @@ -5480,13 +5480,13 @@ } }, "aws-cdk-lib": { - "version": "2.186.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.186.0.tgz", - "integrity": "sha512-y/DD4h8CbhwGyPTpoHELATavZe5FWcy1xSuLlReOd3+cCRZ9rAzVSFdPB8kSJUD4nBPrIeGkW1u8ItUOhms17w==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.227", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^40.7.0", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.3.0", diff --git a/examples/http-axum/cdk/package.json b/examples/http-axum/cdk/package.json index d9e6f496..05bcdc49 100644 --- a/examples/http-axum/cdk/package.json +++ b/examples/http-axum/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.3.3" }, "dependencies": { - "aws-cdk-lib": "^2.186.0", + "aws-cdk-lib": "^2.193.0", "cargo-lambda-cdk": "^0.0.17", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From 26d49f1e6b771cd93ba6d0eb97a3f3300f944c75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:36:47 +0100 Subject: [PATCH 167/211] Bump aws-cdk-lib in /examples/advanced-appconfig-feature-flags/cdk (#978) Bumps [aws-cdk-lib](https://github.com/aws/aws-cdk/tree/HEAD/packages/aws-cdk-lib) from 2.159.1 to 2.193.0. - [Release notes](https://github.com/aws/aws-cdk/releases) - [Changelog](https://github.com/aws/aws-cdk/blob/main/CHANGELOG.v2.alpha.md) - [Commits](https://github.com/aws/aws-cdk/commits/v2.193.0/packages/aws-cdk-lib) --- updated-dependencies: - dependency-name: aws-cdk-lib dependency-version: 2.193.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .../cdk/package-lock.json | 140 ++++++++---------- .../cdk/package.json | 2 +- 2 files changed, 66 insertions(+), 76 deletions(-) diff --git a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json index 61c9a537..8f30ffef 100644 --- a/examples/advanced-appconfig-feature-flags/cdk/package-lock.json +++ b/examples/advanced-appconfig-feature-flags/cdk/package-lock.json @@ -8,7 +8,7 @@ "name": "cdk", "version": "0.1.0", "dependencies": { - "aws-cdk-lib": "2.159.1", + "aws-cdk-lib": "2.193.0", "cargo-lambda-cdk": "^0.0.22", "constructs": "^10.0.0", "source-map-support": "^0.5.21" @@ -40,14 +40,10 @@ } }, "node_modules/@aws-cdk/asset-awscli-v1": { - "version": "2.2.203", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", - "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" - }, - "node_modules/@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.233", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.233.tgz", + "integrity": "sha512-OH5ZN1F/0wwOUwzVUSvE0/syUOi44H9the6IG16anlSptfeQ1fvduJazZAKRuJLtautPbiqxllyOrtWh6LhX8A==", + "license": "Apache-2.0" }, "node_modules/@aws-cdk/asset-node-proxy-agent-v6": { "version": "2.1.0", @@ -55,19 +51,20 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "node_modules/@aws-cdk/cloud-assembly-schema": { - "version": "36.3.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", - "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "bundleDependencies": [ "jsonschema", "semver" ], + "license": "Apache-2.0", "dependencies": { - "jsonschema": "^1.4.1", - "semver": "^7.6.3" + "jsonschema": "~1.4.1", + "semver": "^7.7.1" }, "engines": { - "node": ">= 18.18.0" + "node": ">= 14.15.0" } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/jsonschema": { @@ -79,7 +76,7 @@ } }, "node_modules/@aws-cdk/cloud-assembly-schema/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "inBundle": true, "license": "ISC", "bin": { @@ -1288,9 +1285,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.159.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", - "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -1304,21 +1301,21 @@ "yaml", "mime-types" ], + "license": "Apache-2.0", "dependencies": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", + "fs-extra": "^11.3.0", "ignore": "^5.3.2", - "jsonschema": "^1.4.1", + "jsonschema": "^1.5.0", "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "engines": { @@ -1432,12 +1429,22 @@ "license": "MIT" }, "node_modules/aws-cdk-lib/node_modules/fast-uri": { - "version": "3.0.1", + "version": "3.0.6", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], "inBundle": true, - "license": "MIT" + "license": "BSD-3-Clause" }, "node_modules/aws-cdk-lib/node_modules/fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "inBundle": true, "license": "MIT", "dependencies": { @@ -1487,7 +1494,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "inBundle": true, "license": "MIT", "engines": { @@ -1546,7 +1553,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.6.3", + "version": "7.7.1", "inBundle": true, "license": "ISC", "bin": { @@ -1597,7 +1604,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.2", + "version": "6.9.0", "inBundle": true, "license": "BSD-3-Clause", "dependencies": { @@ -1619,13 +1626,6 @@ "node": ">= 10.0.0" } }, - "node_modules/aws-cdk-lib/node_modules/uri-js": { - "version": "4.4.1", - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, "node_modules/aws-cdk-lib/node_modules/yaml": { "version": "1.10.2", "inBundle": true, @@ -3835,6 +3835,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "bin": { "semver": "bin/semver.js" } @@ -4379,14 +4380,9 @@ } }, "@aws-cdk/asset-awscli-v1": { - "version": "2.2.203", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.203.tgz", - "integrity": "sha512-7ZhjD0L62dhWL0yzoLCxvJTU3tLHTz/yEg6GKt3foSj+ljVR1KSP8MuAi+QPb4pT7ZTfVzELMtI36Y/ROuL3ig==" - }, - "@aws-cdk/asset-kubectl-v20": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@aws-cdk/asset-kubectl-v20/-/asset-kubectl-v20-2.1.2.tgz", - "integrity": "sha512-3M2tELJOxQv0apCIiuKQ4pAbncz9GuLwnKFqxifWfe77wuMxyTRPmxssYHs42ePqzap1LT6GDcPygGs+hHstLg==" + "version": "2.2.233", + "resolved": "https://registry.npmjs.org/@aws-cdk/asset-awscli-v1/-/asset-awscli-v1-2.2.233.tgz", + "integrity": "sha512-OH5ZN1F/0wwOUwzVUSvE0/syUOi44H9the6IG16anlSptfeQ1fvduJazZAKRuJLtautPbiqxllyOrtWh6LhX8A==" }, "@aws-cdk/asset-node-proxy-agent-v6": { "version": "2.1.0", @@ -4394,12 +4390,12 @@ "integrity": "sha512-7bY3J8GCVxLupn/kNmpPc5VJz8grx+4RKfnnJiO1LG+uxkZfANZG3RMHhE+qQxxwkyQ9/MfPtTpf748UhR425A==" }, "@aws-cdk/cloud-assembly-schema": { - "version": "36.3.0", - "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-36.3.0.tgz", - "integrity": "sha512-mLSYgcMFTNCXrGAD7xob95p9s47/7WwEWUJiexxM46H2GxiijhlhLQJs31AS5uRRP6Cx1DLEu4qayKAUOOVGrw==", + "version": "41.2.0", + "resolved": "https://registry.npmjs.org/@aws-cdk/cloud-assembly-schema/-/cloud-assembly-schema-41.2.0.tgz", + "integrity": "sha512-JaulVS6z9y5+u4jNmoWbHZRs9uGOnmn/ktXygNWKNu1k6lF3ad4so3s18eRu15XCbUIomxN9WPYT6Ehh7hzONw==", "requires": { - "jsonschema": "^1.4.1", - "semver": "^7.6.3" + "jsonschema": "~1.4.1", + "semver": "^7.7.1" }, "dependencies": { "jsonschema": { @@ -4407,7 +4403,7 @@ "bundled": true }, "semver": { - "version": "7.6.3", + "version": "7.7.1", "bundled": true } } @@ -5358,24 +5354,23 @@ } }, "aws-cdk-lib": { - "version": "2.159.1", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.159.1.tgz", - "integrity": "sha512-zcOyAs3+DTu+CtLehdOgvyosZ7nbLZ+OfBE6uVNMshXm957oXJrLsu6hehLt81TDxfItWYNluFcXkwepZDm6Ng==", + "version": "2.193.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.193.0.tgz", + "integrity": "sha512-Bsf11FM85+s9jSAT8JfDNlrSz6LF3Xa2eSNOyMLcXNopI7eVXP1U6opRHK0waaZGLmQfOwsbSp/9XRMKikkazg==", "requires": { - "@aws-cdk/asset-awscli-v1": "^2.2.202", - "@aws-cdk/asset-kubectl-v20": "^2.1.2", + "@aws-cdk/asset-awscli-v1": "^2.2.229", "@aws-cdk/asset-node-proxy-agent-v6": "^2.1.0", - "@aws-cdk/cloud-assembly-schema": "^36.0.24", + "@aws-cdk/cloud-assembly-schema": "^41.0.0", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", - "fs-extra": "^11.2.0", + "fs-extra": "^11.3.0", "ignore": "^5.3.2", - "jsonschema": "^1.4.1", + "jsonschema": "^1.5.0", "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.6.3", - "table": "^6.8.2", + "semver": "^7.7.1", + "table": "^6.9.0", "yaml": "1.10.2" }, "dependencies": { @@ -5448,11 +5443,11 @@ "bundled": true }, "fast-uri": { - "version": "3.0.1", + "version": "3.0.6", "bundled": true }, "fs-extra": { - "version": "11.2.0", + "version": "11.3.0", "bundled": true, "requires": { "graceful-fs": "^4.2.0", @@ -5485,7 +5480,7 @@ } }, "jsonschema": { - "version": "1.4.1", + "version": "1.5.0", "bundled": true }, "lodash.truncate": { @@ -5519,7 +5514,7 @@ "bundled": true }, "semver": { - "version": "7.6.3", + "version": "7.7.1", "bundled": true }, "slice-ansi": { @@ -5548,7 +5543,7 @@ } }, "table": { - "version": "6.8.2", + "version": "6.9.0", "bundled": true, "requires": { "ajv": "^8.0.1", @@ -5562,12 +5557,6 @@ "version": "2.0.1", "bundled": true }, - "uri-js": { - "version": "4.4.1", - "requires": { - "punycode": "^2.1.0" - } - }, "yaml": { "version": "1.10.2", "bundled": true @@ -7200,7 +7189,8 @@ "semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true }, "shebang-command": { "version": "2.0.0", diff --git a/examples/advanced-appconfig-feature-flags/cdk/package.json b/examples/advanced-appconfig-feature-flags/cdk/package.json index 83cb9552..688a31a0 100644 --- a/examples/advanced-appconfig-feature-flags/cdk/package.json +++ b/examples/advanced-appconfig-feature-flags/cdk/package.json @@ -20,7 +20,7 @@ "typescript": "~5.4.5" }, "dependencies": { - "aws-cdk-lib": "2.159.1", + "aws-cdk-lib": "2.193.0", "cargo-lambda-cdk": "^0.0.22", "constructs": "^10.0.0", "source-map-support": "^0.5.21" From 5457a77dd1512fb693059c4ea298b54b4eb0cd7b Mon Sep 17 00:00:00 2001 From: Falk Woldmann <52786457+FalkWoldmann@users.noreply.github.com> Date: Wed, 30 Apr 2025 14:42:28 +0200 Subject: [PATCH 168/211] Fix typos (#952) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1a93d85c..882c9a79 100644 --- a/README.md +++ b/README.md @@ -82,7 +82,7 @@ when a function invocation fails, AWS Lambda expects you to return an object tha } ``` -The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the converstion of several general errors types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. +The Rust Runtime for Lambda uses a struct called `Diagnostic` to represent function errors internally. The runtime implements the conversion of several general error types, like `std::error::Error`, into `Diagnostic`. For these general implementations, the `error_type` is the name of the value type returned by your function. For example, if your function returns `lambda_runtime::Error`, the `error_type` will be something like `alloc::boxed::Box`, which is not very descriptive. ### Implement your own Diagnostic From 4c56306eee6bdf4669372ab696d445656715fd15 Mon Sep 17 00:00:00 2001 From: braveocheretovych Date: Wed, 30 Apr 2025 15:48:08 +0300 Subject: [PATCH 169/211] docs: replaced the link to the shield (#968) * Update README.md * Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 882c9a79..71d8e57b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Rust Runtime for AWS Lambda -[![Build Status](https://github.com/awslabs/aws-lambda-rust-runtime/workflows/Rust/badge.svg)](https://github.com/awslabs/aws-lambda-rust-runtime/actions) +[![Build Status](https://github.com/awslabs/aws-lambda-rust-runtime/actions/workflows/check-examples.yml/badge.svg)](https://github.com/awslabs/aws-lambda-rust-runtime/actions) This package makes it easy to run AWS Lambda Functions written in Rust. This workspace includes multiple crates: From c511046a3305bccf71fea3fad15d65826e00db23 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 May 2025 01:45:45 +0100 Subject: [PATCH 170/211] Update opentelemetry-semantic-conventions requirement from 0.27 to 0.29 (#970) * Update opentelemetry-semantic-conventions requirement from 0.27 to 0.29 Updates the requirements on [opentelemetry-semantic-conventions](https://github.com/open-telemetry/opentelemetry-rust) to permit the latest version. - [Release notes](https://github.com/open-telemetry/opentelemetry-rust/releases) - [Commits](https://github.com/open-telemetry/opentelemetry-rust/compare/opentelemetry-semantic-conventions-0.27.0...opentelemetry-semantic-conventions-0.29.0) --- updated-dependencies: - dependency-name: opentelemetry-semantic-conventions dependency-type: direct:production ... Signed-off-by: dependabot[bot] * fix: otel example * bump: MSRV to 1.75.0 * bump: MSRV to 1.81.0 * fix: cargo fmt --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Maxime David --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- README.md | 2 +- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/layers/otel.rs | 8 ++++---- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 6894e331..172bcdd8 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index cb23c289..0f151f43 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index dfc59ee8..8720af17 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.71.1" # Current MSRV + - "1.81.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/README.md b/README.md index 71d8e57b..40fa7a13 100644 --- a/README.md +++ b/README.md @@ -409,7 +409,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.71.1, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.81.0, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index b4a7ad3d..5cf67ae2 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -41,7 +41,7 @@ hyper-util = { workspace = true, features = [ ] } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.27", optional = true, features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index f50f36f7..42b507f8 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -1,7 +1,7 @@ use std::{fmt::Display, future::Future, pin::Pin, task}; use crate::LambdaInvocation; -use opentelemetry_semantic_conventions::trace as traceconv; +use opentelemetry_semantic_conventions::attribute; use pin_project::pin_project; use tower::{Layer, Service}; use tracing::{field, instrument::Instrumented, Instrument}; @@ -76,9 +76,9 @@ where "Lambda function invocation", "otel.name" = req.context.env_config.function_name, "otel.kind" = field::Empty, - { traceconv::FAAS_TRIGGER } = &self.otel_attribute_trigger, - { traceconv::FAAS_INVOCATION_ID } = req.context.request_id, - { traceconv::FAAS_COLDSTART } = self.coldstart + { attribute::FAAS_TRIGGER } = &self.otel_attribute_trigger, + { attribute::FAAS_INVOCATION_ID } = req.context.request_id, + { attribute::FAAS_COLDSTART } = self.coldstart ); // After the first execution, we can set 'coldstart' to false From c78164e88c153abf4f2aa794c52e772ebda57997 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:08:52 -0700 Subject: [PATCH 171/211] feat(tracing, lambda-runtime): add support for custom writer with default tracing subscriber, add turnkey graceful shutdown helper behind 'graceful-shutdown' feature flag (#982) * feat(tracing): add `init_default_subscriber_with_writer()` * feat(lambda_runtime): add `spawn_graceful_shutdown_handler`, behind `graceful-shutdown` feature flag * chore(runtime-api-client/docs): add docs.rs metadata indicating that tracing module requires tracing feature --- README.md | 46 +++++++++ lambda-integration-tests/src/helloworld.rs | 1 + lambda-runtime-api-client/src/lib.rs | 1 + lambda-runtime-api-client/src/tracing.rs | 35 ++++++- lambda-runtime/Cargo.toml | 8 ++ lambda-runtime/src/lib.rs | 108 +++++++++++++++++++++ 6 files changed, 197 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40fa7a13..981633c9 100644 --- a/README.md +++ b/README.md @@ -126,6 +126,52 @@ async fn handler(_event: LambdaEvent) -> Result<(), Diagnostic> { You can see more examples on how to use these error crates in our [example repository](https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples/basic-error-error-crates-integration). +### Graceful shutdown + +`lambda_runtime` offers a helper to simplify configuring graceful shutdown signal handling, `spawn_graceful_shutdown_handler()`. This requires the `graceful-shutdown` feature flag and only supports Unix systems. + +You can use it by passing a `FnOnce` closure that returns an async block. That async block will be executed +when the function receives a `SIGTERM` or `SIGKILL`. + +Note that this helper is opinionated in a number of ways. Most notably: +1. It spawns a task to drive your signal handlers +2. It registers a 'no-op' extension in order to enable graceful shutdown signals +3. It panics on unrecoverable errors + +If you prefer to fine-tune the behavior, refer to the implementation of `spawn_graceful_shutdown_handler()` as a starting point for your own. + +For more information on graceful shutdown handling in AWS Lambda, see: [aws-samples/graceful-shutdown-with-aws-lambda](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda). + +Complete example (cleaning up a non-blocking tracing writer): + +```rust,no_run +use lambda_runtime::{service_fn, LambdaEvent, Error}; +use serde_json::{json, Value}; + +#[tokio::main] +async fn main() -> Result<(), Error> { + let func = service_fn(func); + + let (writer, log_guard) = tracing_appender::non_blocking(std::io::stdout()); + lambda_runtime::tracing::init_default_subscriber_with_writer(writer); + + let shutdown_hook = || async move { + std::mem::drop(log_guard); + }; + lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); + + lambda_runtime::run(func).await?; + Ok(()) +} + +async fn func(event: LambdaEvent) -> Result { + let (event, _context) = event.into_parts(); + let first_name = event["firstName"].as_str().unwrap_or("world"); + + Ok(json!({ "message": format!("Hello, {}!", first_name) })) +} +``` + ## Building and deploying your Lambda functions If you already have Cargo Lambda installed in your machine, run the next command to build your function: diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index 9989da43..92fc7d80 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -8,6 +8,7 @@ use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; async fn main() -> Result<(), Error> { tracing::init_default_subscriber(); let func = service_fn(func); + lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}); lambda_runtime::run(func).await?; Ok(()) } diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 00c00e4c..78b51db1 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -22,6 +22,7 @@ pub use error::*; pub mod body; #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub mod tracing; /// API client to interact with the AWS Lambda Runtime API. diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 09f41991..79216cf7 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -14,12 +14,16 @@ pub use tracing::*; /// Re-export the `tracing-subscriber` crate to build your own subscribers. pub use tracing_subscriber as subscriber; +use tracing_subscriber::fmt::MakeWriter; const DEFAULT_LOG_LEVEL: &str = "INFO"; /// Initialize `tracing-subscriber` with default logging options. /// -/// This function uses environment variables set with [Lambda's advance logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// The default subscriber writes logs to STDOUT in the current context. +/// If you want to customize the writer, see [`init_default_subscriber_with_writer()`]. +/// +/// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. /// /// This subscriber sets the logging level based on environment variables: @@ -31,6 +35,32 @@ const DEFAULT_LOG_LEVEL: &str = "INFO"; /// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, /// otherwise they will be formatted with the default tracing format. pub fn init_default_subscriber() { + init_default_subscriber_with_writer(std::io::stdout); +} + +/// Initialize `tracing-subscriber` with default logging options, and a custom writer. +/// +/// You might want to avoid writing to STDOUT in the local context via [`init_default_subscriber()`], if you have a high-throughput Lambdas that involve +/// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). +/// In that case, you might prefer to use [tracing_appender::NonBlocking] instead - particularly if your Lambda is fairly long-running and stays warm. +/// Though, note that you are then responsible +/// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. +/// +/// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) +/// if they're configured for your function. +/// +/// This subscriber sets the logging level based on environment variables: +/// - if `AWS_LAMBDA_LOG_LEVEL` is set, it takes precedence over any other environment variables. +/// - if `AWS_LAMBDA_LOG_LEVEL` is not set, check if `RUST_LOG` is set. +/// - if none of those two variables are set, use `INFO` as the logging level. +/// +/// The logging format can also be changed based on Lambda's advanced logging controls. +/// If the `AWS_LAMBDA_LOG_FORMAT` environment variable is set to `JSON`, the log lines will be formatted as json objects, +/// otherwise they will be formatted with the default tracing format. +pub fn init_default_subscriber_with_writer(writer: Writer) +where + Writer: for<'writer> MakeWriter<'writer> + Send + Sync + 'static, +{ let log_format = env::var("AWS_LAMBDA_LOG_FORMAT").unwrap_or_default(); let log_level_str = env::var("AWS_LAMBDA_LOG_LEVEL").or_else(|_| env::var("RUST_LOG")); let log_level = @@ -43,7 +73,8 @@ pub fn init_default_subscriber() { EnvFilter::builder() .with_default_directive(log_level.into()) .from_env_lossy(), - ); + ) + .with_writer(writer); if log_format.eq_ignore_ascii_case("json") { collector.json().init() diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 5cf67ae2..fb68aa76 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -20,6 +20,10 @@ opentelemetry = ["opentelemetry-semantic-conventions"] # enables access to the O anyhow = ["dep:anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info eyre = ["dep:eyre"] # enables From for Diagnostic for eyre error types, see README.md for more info miette = ["dep:miette"] # enables From for Diagnostic for miette error types, see README.md for more info +# TODO: remove tokio/rt and rt-multi-thread from non-feature-flagged dependencies in new breaking version, since they are unused: +# as well as default features +# https://github.com/awslabs/aws-lambda-rust-runtime/issues/984 +graceful-shutdown = ["tokio/rt", "tokio/signal", "dep:lambda-extension"] [dependencies] anyhow = { version = "1.0.86", optional = true } @@ -39,6 +43,7 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } +lambda-extension = { version = "0.11.0", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } @@ -67,4 +72,7 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } +# Self dependency to enable the graceful-shutdown feature for tests +lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } +tracing-appender = "0.2" diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 76d0562a..e1b15a25 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -32,6 +32,7 @@ pub mod streaming; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub use lambda_runtime_api_client::tracing; /// Types available to a Lambda function. @@ -123,3 +124,110 @@ where let runtime = Runtime::new(handler).layer(layers::TracingLayer::new()); runtime.run().await } + +/// Spawns a task that will be execute a provided async closure when the process +/// receives unix graceful shutdown signals. If the closure takes longer than 500ms +/// to execute, an unhandled `SIGKILL` signal might be received. +/// +/// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. +/// +/// This function must be called prior to [lambda_runtime::run()]. +/// +/// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. +/// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name +/// can not be reused by other registered extensions. This is necessary in order to receive graceful shutdown signals. +/// +/// This extension is cheap to run because it receives no events, but is not zero cost. If you have another extension +/// registered already, you might prefer to manually construct your own graceful shutdown handling without the dummy extension. +/// +/// For more information on general AWS Lambda graceful shutdown handling, see: +/// https://github.com/aws-samples/graceful-shutdown-with-aws-lambda +/// +/// # Panics +/// +/// This function panics if: +/// - this function is called after [lambda_runtime::run()] +/// - this function is called outside of a context that has access to the tokio i/o +/// - the no-op extension cannot be registered +/// - either signal listener panics [tokio::signal::unix](https://docs.rs/tokio/latest/tokio/signal/unix/fn.signal.html#errors) +/// +/// # Example +/// ```no_run +/// use lambda_runtime::{Error, service_fn, LambdaEvent}; +/// use serde_json::Value; +/// +/// #[tokio::main] +/// async fn main() -> Result<(), Error> { +/// let func = service_fn(func); +/// +/// let (writer, log_guard) = tracing_appender::non_blocking(std::io::stdout()); +/// lambda_runtime::tracing::init_default_subscriber_with_writer(writer); +/// +/// let shutdown_hook = || async move { +/// std::mem::drop(log_guard); +/// }; +/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); +/// +/// lambda_runtime::run(func).await?; +/// Ok(()) +/// } +/// +/// async fn func(event: LambdaEvent) -> Result { +/// Ok(event.payload) +/// } +/// ``` +#[cfg(all(unix, feature = "graceful-shutdown"))] +#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] +pub fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) +where + Fut: Future + Send + 'static, +{ + tokio::task::spawn(async move { + // You need an extension registered with the Lambda orchestrator in order for your process + // to receive a SIGTERM for graceful shutdown. + // + // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. + // + // This extension is cheap to run since after it connects to the lambda orchestration, the connection + // will just wait forever for data to come, which never comes, so it won't cause wakes. + let extension = lambda_extension::Extension::new() + // Don't subscribe to any event types + .with_events(&[]) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await + .expect("could not register no-op extension for graceful shutdown"); + + let graceful_shutdown_future = async move { + let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()).unwrap(); + let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap(); + tokio::select! { + _sigint = sigint.recv() => { + eprintln!("[runtime] SIGINT received"); + eprintln!("[runtime] Graceful shutdown in progress ..."); + shutdown_hook().await; + eprintln!("[runtime] Graceful shutdown completed"); + std::process::exit(0); + }, + _sigterm = sigterm.recv()=> { + eprintln!("[runtime] SIGTERM received"); + eprintln!("[runtime] Graceful shutdown in progress ..."); + shutdown_hook().await; + eprintln!("[runtime] Graceful shutdown completed"); + std::process::exit(0); + }, + } + }; + + // TODO: add biased! to always poll the signal handling future first, once supported: + // https://github.com/tokio-rs/tokio/issues/7304 + let _: (_, ()) = tokio::join!(graceful_shutdown_future, async { + // we suppress extension errors because we don't actually mind if it crashes, + // all we need to do is kick off the run so that lambda exits the init phase + let _ = extension.run().await; + }); + }); +} From 86da85806f6feb206224b551c3469d450582ab3e Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:09:09 -0700 Subject: [PATCH 172/211] chore: add msrv in Cargo manifests (#987) --- lambda-events/Cargo.toml | 1 + lambda-extension/Cargo.toml | 1 + lambda-http/Cargo.toml | 1 + lambda-integration-tests/Cargo.toml | 1 + lambda-runtime-api-client/Cargo.toml | 1 + lambda-runtime/Cargo.toml | 1 + 6 files changed, 6 insertions(+) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b8d34055..4e9b4f79 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,7 @@ [package] name = "aws_lambda_events" version = "0.16.0" +rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 16b6dace..37c5ee2d 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -2,6 +2,7 @@ name = "lambda-extension" version = "0.11.0" edition = "2021" +rust-version = "1.81.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 46198dca..4f5ce78c 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -6,6 +6,7 @@ authors = [ "Harold Sun ", ] edition = "2021" +rust-version = "1.81.0" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index ee44a969..d7c91088 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -3,6 +3,7 @@ name = "aws_lambda_rust_integration_tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" +rust-version = "1.81.0" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 57fc4bca..d027f0cd 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -2,6 +2,7 @@ name = "lambda_runtime_api_client" version = "0.11.1" edition = "2021" +rust-version = "1.81.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index fb68aa76..68aa713a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -7,6 +7,7 @@ authors = [ ] description = "AWS Lambda Runtime" edition = "2021" +rust-version = "1.81.0" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] From a5989949101086ca379644b1ada293d5d171e61f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 01:23:04 -0700 Subject: [PATCH 173/211] fix(docs): examples/extension-internal-flush: don't refer to a standalone layer arn for internal extension deploy, add testing information (#980) --- examples/extension-internal-flush/README.md | 18 +++++++++++++++++- examples/extension-internal-flush/src/main.rs | 2 ++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/examples/extension-internal-flush/README.md b/examples/extension-internal-flush/README.md index 553f7a3d..c6408676 100644 --- a/examples/extension-internal-flush/README.md +++ b/examples/extension-internal-flush/README.md @@ -18,9 +18,10 @@ the Lambda service returns the response to the caller immediately. Extensions ma without introducing an observable delay. ## Build & Deploy +Two deploy options for internal extensions: 1. Install [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) -2. Build the extension with `cargo lambda build --release` +2. Build a function with the internal extension embedded with `cargo lambda build --release` 3. Deploy the function to AWS Lambda with `cargo lambda deploy --iam-role YOUR_ROLE` The last command will give you an ARN for the extension layer that you can use in your functions. @@ -28,3 +29,18 @@ The last command will give you an ARN for the extension layer that you can use i ## Build for ARM 64 Build the extension with `cargo lambda build --release --arm64` + + +## Test your Function + +From your local: +``` +cargo lambda watch +# in new terminal window +cargo lambda invoke --data-ascii '{"Records":[{"messageId":"MessageID_1","receiptHandle":"MessageReceiptHandle","body":"{\"a\":\"Test\",\"b\":123}"}]}' +``` + +If you have deployed to AWS using the commands in the 'Build & Deploy' section, you can also invoke your function remotely: +``` +cargo lambda invoke extension-internal-flush --remote --data-ascii '{"Records":[{"messageId":"MessageID_1","receiptHandle":"MessageReceiptHandle","body":"{\"a\":\"Test\",\"b\":123}"}]}' +``` \ No newline at end of file diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index ff1d10da..c728030b 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -101,6 +101,8 @@ async fn main() -> Result<(), Error> { let handler = Arc::new(EventHandler::new(request_done_sender)); + // TODO: add biased! to always poll the handler future first, once supported: + // https://github.com/tokio-rs/tokio/issues/7304 tokio::try_join!( lambda_runtime::run(service_fn(|event| { let handler = handler.clone(); From 56a23bf66345257267a9934c8d2ba1a74efd7c2f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 08:09:10 -0700 Subject: [PATCH 174/211] fix(integ tests): enable graceful-shutdown feature flag; chore(ci/cd): build integration test crate as part of PR validation (#988) * fix(integ tests): enable graceful-shutdown feature flag * chore(ci/cd): build integration test crate as part of PR validation --- .github/workflows/build-integration-test.yml | 37 ++++++++++++++++++++ lambda-integration-tests/Cargo.toml | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build-integration-test.yml diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml new file mode 100644 index 00000000..3400b052 --- /dev/null +++ b/.github/workflows/build-integration-test.yml @@ -0,0 +1,37 @@ +name: Build integration tests + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + - 'lambda-extension/**' + - 'Cargo.toml' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + - 'lambda-extension/**' + - 'Cargo.toml' + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.81.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build Integration tests + uses: ./.github/actions/rust-build + with: + package: lambda_integration_tests + toolchain: ${{ matrix.toolchain}} diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index d7c91088..555840f3 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -12,7 +12,7 @@ keywords = ["AWS", "Lambda", "API"] readme = "../README.md" [dependencies] -lambda_runtime = { path = "../lambda-runtime" } +lambda_runtime = { path = "../lambda-runtime", features = ["tracing", "graceful-shutdown"] } aws_lambda_events = { path = "../lambda-events" } serde_json = "1.0.121" tokio = { version = "1", features = ["full"] } From 57cd659a77fa827791ed196781133a054c21f029 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 8 May 2025 08:43:00 -0700 Subject: [PATCH 175/211] fix(ci/cd): pin cargo-lambda back to 1.8.1 temporarily (#989) --- .github/workflows/run-integration-test.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index f1e30d09..a492e50d 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,6 +18,8 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 + # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed + tag: v1.8.1 - name: install Zig toolchain uses: korandoru/setup-zig@v1 with: From 661cc2642e3071ce133cdb43327aa44df0781df4 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Fri, 16 May 2025 09:07:17 -0700 Subject: [PATCH 176/211] fix(lambda-runtime,lambda-integration-tests): make spawn_graceful_shutdown_handler() async, await the extension being registered before spawning background extension handler task (#992) --- lambda-integration-tests/src/helloworld.rs | 2 +- lambda-runtime/Cargo.toml | 3 ++ lambda-runtime/src/lib.rs | 42 +++++++++++----------- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index 92fc7d80..b40cd0c6 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -8,7 +8,7 @@ use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; async fn main() -> Result<(), Error> { tracing::init_default_subscriber(); let func = service_fn(func); - lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}); + lambda_runtime::spawn_graceful_shutdown_handler(|| async move {}).await; lambda_runtime::run(func).await?; Ok(()) } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 68aa713a..4c4cab2a 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -73,6 +73,9 @@ hyper-util = { workspace = true, features = [ "server-auto", "tokio", ] } +# pin back to pre-1.2.1 to avoid breaking rust MSRV of 1.81: +# https://github.com/hsivonen/idna_adapter/commit/f948802e3a2ae936eec51886eefbd7d536a28791 +idna_adapter = "=1.2.0" # Self dependency to enable the graceful-shutdown feature for tests lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e1b15a25..5598d104 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -131,7 +131,7 @@ where /// /// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. /// -/// This function must be called prior to [lambda_runtime::run()]. +/// This function's returned future must be resolved prior to [lambda_runtime::run()]. /// /// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. /// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name @@ -166,7 +166,7 @@ where /// let shutdown_hook = || async move { /// std::mem::drop(log_guard); /// }; -/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); +/// lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook).await; /// /// lambda_runtime::run(func).await?; /// Ok(()) @@ -178,29 +178,29 @@ where /// ``` #[cfg(all(unix, feature = "graceful-shutdown"))] #[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] -pub fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) +pub async fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) where Fut: Future + Send + 'static, { - tokio::task::spawn(async move { - // You need an extension registered with the Lambda orchestrator in order for your process - // to receive a SIGTERM for graceful shutdown. - // - // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. - // - // This extension is cheap to run since after it connects to the lambda orchestration, the connection - // will just wait forever for data to come, which never comes, so it won't cause wakes. - let extension = lambda_extension::Extension::new() - // Don't subscribe to any event types - .with_events(&[]) - // Internal extension names MUST be unique within a given Lambda function. - .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") - // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init - // phase and begins the Invoke phase. - .register() - .await - .expect("could not register no-op extension for graceful shutdown"); + // You need an extension registered with the Lambda orchestrator in order for your process + // to receive a SIGTERM for graceful shutdown. + // + // We accomplish this here by registering a no-op internal extension, which does not subscribe to any events. + // + // This extension is cheap to run since after it connects to the lambda orchestration, the connection + // will just wait forever for data to come, which never comes, so it won't cause wakes. + let extension = lambda_extension::Extension::new() + // Don't subscribe to any event types + .with_events(&[]) + // Internal extension names MUST be unique within a given Lambda function. + .with_extension_name("_lambda-rust-runtime-no-op-graceful-shutdown-helper") + // Extensions MUST be registered before calling lambda_runtime::run(), which ends the Init + // phase and begins the Invoke phase. + .register() + .await + .expect("could not register no-op extension for graceful shutdown"); + tokio::task::spawn(async move { let graceful_shutdown_future = async move { let mut sigint = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::interrupt()).unwrap(); let mut sigterm = tokio::signal::unix::signal(tokio::signal::unix::SignalKind::terminate()).unwrap(); From 00fef2c50c6ac722b00a49fe9bc3fa0d85db7894 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sat, 17 May 2025 00:32:29 +0800 Subject: [PATCH 177/211] Release runtime 0.14.0 with graceful shutdown hook (#991) * Release runtime 0.14.0 with graceful shutdown hook lambda-runtime-api-clietn 0.12.0 lambda-runtime 0.14.0 lambda-http 0.15.0 lambda-extesnion 0.12.0 * Update lambda-extension version in lambda-runtime * Fix integration tests --- .github/workflows/build-integration-test.yml | 2 +- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 6 +++--- lambda-integration-tests/Cargo.toml | 2 +- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 3400b052..6f7ad592 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -33,5 +33,5 @@ jobs: - name: Build Integration tests uses: ./.github/actions/rust-build with: - package: lambda_integration_tests + package: lambda-integration-tests toolchain: ${{ matrix.toolchain}} diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 37c5ee2d..e9ff826b 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.11.0" +version = "0.12.0" edition = "2021" rust-version = "1.81.0" authors = [ @@ -26,7 +26,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.11", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4f5ce78c..904d26af 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.14.0" +version = "0.15.0" authors = [ "David Calavera ", "Harold Sun ", @@ -39,7 +39,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.13.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.0", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -58,7 +58,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 555840f3..f0bdb292 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "aws_lambda_rust_integration_tests" +name = "lambda-integration-tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index d027f0cd..65c5b130 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.11.1" +version = "0.12.0" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 4c4cab2a..7ffca008 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.13.0" +version = "0.14.0" authors = [ "David Calavera ", "Harold Sun ", @@ -44,8 +44,8 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda-extension = { version = "0.11.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.11.1", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 489b2359d8fd7cb7296001fce729f60f7bdbb372 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 27 May 2025 03:47:57 -0700 Subject: [PATCH 178/211] CI/CD fixes (#994) * chore(lambda_runtime): fix clippy by boxing large service future variant that models LambdaInvocation->LambdaEvent processing failures * fix(ci/cd): build but do not execute tests in `build integration tests` step --- .github/actions/rust-build/action.yml | 5 +++++ .github/workflows/build-integration-test.yml | 3 +++ lambda-runtime/src/layers/api_response.rs | 16 ++++++++++------ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.github/actions/rust-build/action.yml b/.github/actions/rust-build/action.yml index 85b5c0e8..6c393d1c 100644 --- a/.github/actions/rust-build/action.yml +++ b/.github/actions/rust-build/action.yml @@ -7,6 +7,10 @@ inputs: toolchain: required: true description: "the Rust toolchain to use" + run-tests: + required: true + default: true + description: "whether to run tests in addition to building" runs: using: "composite" @@ -22,5 +26,6 @@ runs: run: cargo build --all-features --verbose --package ${{ inputs.package }} - name: Run tests + if: ${{ inputs.run-tests == 'true' }} shell: bash run: cargo test --all-features --verbose --package ${{ inputs.package }} diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index 6f7ad592..c0d43e25 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -35,3 +35,6 @@ jobs: with: package: lambda-integration-tests toolchain: ${{ matrix.toolchain}} + # the tests will generally fail in ci since they make a network call to a real endpoint, + # this step is just designed to make sure they build successfully + run-tests: false diff --git a/lambda-runtime/src/layers/api_response.rs b/lambda-runtime/src/layers/api_response.rs index e744cde1..453f8b4c 100644 --- a/lambda-runtime/src/layers/api_response.rs +++ b/lambda-runtime/src/layers/api_response.rs @@ -85,7 +85,7 @@ where #[cfg(debug_assertions)] if req.parts.status.is_server_error() { error!("Lambda Runtime server returned an unexpected error"); - return RuntimeApiResponseFuture::Ready(Some(Err(req.parts.status.to_string().into()))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(req.parts.status.to_string().into())))); } // Utility closure to propagate potential error from conditionally executed trace @@ -98,22 +98,23 @@ where }; if let Err(err) = trace_fn() { error!(error = ?err, "Failed to parse raw JSON event received from Lambda. The handler will not be called. Log at TRACE level to see the payload."); - return RuntimeApiResponseFuture::Ready(Some(Err(err))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(err)))); }; let request_id = req.context.request_id.clone(); let lambda_event = match deserializer::deserialize::(&req.body, req.context) { Ok(lambda_event) => lambda_event, Err(err) => match build_event_error_request(&request_id, err) { - Ok(request) => return RuntimeApiResponseFuture::Ready(Some(Ok(request))), + Ok(request) => return RuntimeApiResponseFuture::Ready(Box::new(Some(Ok(request)))), Err(err) => { error!(error = ?err, "failed to build error response for Lambda Runtime API"); - return RuntimeApiResponseFuture::Ready(Some(Err(err))); + return RuntimeApiResponseFuture::Ready(Box::new(Some(Err(err)))); } }, }; - // Once the handler input has been generated successfully, the + // Once the handler input has been generated successfully, pass it through to inner services + // allowing processing both before reaching the handler function and after the handler completes. let fut = self.inner.call(lambda_event); RuntimeApiResponseFuture::Future(fut, request_id, PhantomData) } @@ -141,7 +142,10 @@ pub enum RuntimeApiResponseFuture, ), - Ready(Option, BoxError>>), + /// This variant is used in case the invocation fails to be processed into an event. + /// We box it to avoid bloating the size of the more likely variant, which is + /// the future that drives event processing. + Ready(Box, BoxError>>>), } impl Future From 5d3e91624bafb7e5c035422eafefc51b9f4855c6 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 27 May 2025 19:54:56 -0700 Subject: [PATCH 179/211] feat(ci): build and check rustdocs for warnings; fix(docs): add missing cfg flag to allow displaying features on doc.rs, fix various warnings (#998) * feat(ci): check rustdocs on build * fix(docs): enable `#![cfg_attr(docsrs, feature(doc_cfg))]` to allow rustdoc feature display * fix(docs): fix assorted rustdoc warnings --- .github/workflows/check-docs.yml | 39 +++++++++++++++++++ .../src/main.rs | 2 +- examples/basic-error-handling/src/main.rs | 2 +- examples/http-basic-lambda/src/main.rs | 2 +- examples/http-dynamodb/src/main.rs | 2 +- examples/http-query-parameters/src/main.rs | 2 +- .../src/custom_serde/float_unix_epoch.rs | 1 - lambda-events/src/custom_serde/headers.rs | 2 +- lambda-events/src/event/appsync/mod.rs | 2 +- .../src/event/cloudformation/provider.rs | 2 +- .../src/event/cloudwatch_alarms/mod.rs | 2 +- .../src/event/cloudwatch_events/mod.rs | 2 +- lambda-events/src/event/codebuild/mod.rs | 2 +- lambda-events/src/event/codedeploy/mod.rs | 2 +- .../src/event/codepipeline_cloudwatch/mod.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 8 ++-- lambda-events/src/event/eventbridge/mod.rs | 2 +- lambda-events/src/event/iot/mod.rs | 4 +- lambda-events/src/event/kinesis/event.rs | 2 +- lambda-events/src/event/s3/object_lambda.rs | 2 +- lambda-events/src/event/sqs/mod.rs | 2 +- lambda-events/src/lib.rs | 1 + lambda-events/src/time_window.rs | 12 +++--- lambda-extension/src/lib.rs | 1 + lambda-extension/src/logs.rs | 2 +- lambda-http/src/lib.rs | 3 +- lambda-http/src/response.rs | 2 +- lambda-runtime-api-client/src/body/channel.rs | 4 +- lambda-runtime-api-client/src/body/mod.rs | 2 +- lambda-runtime-api-client/src/body/sender.rs | 2 +- lambda-runtime-api-client/src/body/watch.rs | 2 +- lambda-runtime-api-client/src/error.rs | 2 +- lambda-runtime-api-client/src/lib.rs | 1 + lambda-runtime-api-client/src/tracing.rs | 2 +- lambda-runtime/src/layers/otel.rs | 2 +- lambda-runtime/src/lib.rs | 7 ++-- 36 files changed, 87 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/check-docs.yml diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml new file mode 100644 index 00000000..4e26c31c --- /dev/null +++ b/.github/workflows/check-docs.yml @@ -0,0 +1,39 @@ +name: Check rustdocs +# this is its own workflow since we to to use unstable +# to have the docs.rs display of feature flags + +on: + push: + paths: + - 'lambda-runtime/**' + - 'lambda-runtime-api-client/**' + - 'lambda-http/**' + - 'lambda-events/**' + - 'lambda-extension/**' + - 'Cargo.toml' + + pull_request: + paths: + - 'lambda-runtime/**' + - 'lambda-runtime-api-client/**' + - 'lambda-http/**' + - 'lambda-events/**' + - 'lambda-extension/**' + - 'Cargo.toml' + +jobs: + build-runtime: + runs-on: ubuntu-latest + + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@nightly + + - name: Check documentation + shell: bash + env: + RUSTFLAGS: --cfg docsrs + RUSTDOCFLAGS: --cfg docsrs -Dwarnings + run: cargo doc --no-deps --document-private-items --all-features diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 42bb2253..be4eb66f 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -44,7 +44,7 @@ async fn main() -> Result<(), Error> { /// Important note: your lambda sqs trigger *needs* to be configured with partial batch response support /// with the ` ReportBatchItemFailures` flag set to true, otherwise failed message will be dropped, /// for more details see: -/// https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting +/// /// /// /// Note that if you are looking for parallel processing (multithread) instead of concurrent processing, diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 3bc76936..12c954a9 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -1,4 +1,4 @@ -/// See https://github.com/awslabs/aws-lambda-rust-runtime for more info on Rust runtime for AWS Lambda +/// See for more info on Rust runtime for AWS Lambda use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; use serde_json::json; diff --git a/examples/http-basic-lambda/src/main.rs b/examples/http-basic-lambda/src/main.rs index d0e41561..9db6b275 100644 --- a/examples/http-basic-lambda/src/main.rs +++ b/examples/http-basic-lambda/src/main.rs @@ -3,7 +3,7 @@ use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; /// This is the main body for the function. /// Write your code inside it. /// There are some code examples in the Runtime repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn function_handler(_event: Request) -> Result, Error> { // Extract some useful information from the request diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index e5cbb2a3..2bf3fb9d 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -15,7 +15,7 @@ pub struct Item { /// This is the main body for the function. /// Write your code inside it. /// You can see more examples in Runtime's repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn handle_request(db_client: &Client, event: Request) -> Result, Error> { // Extract some useful information from the request let body = event.body(); diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index 1f7110a5..ef9cf658 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -3,7 +3,7 @@ use lambda_http::{run, service_fn, tracing, Error, IntoResponse, Request, Reques /// This is the main body for the function. /// Write your code inside it. /// You can see more examples in Runtime's repository: -/// - https://github.com/awslabs/aws-lambda-rust-runtime/tree/main/examples +/// - async fn function_handler(event: Request) -> Result { // Extract some useful information from the request Ok( diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index 805c672f..f4907a1f 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -57,7 +57,6 @@ where struct SecondsFloatTimestampVisitor; /// Serialize a UTC datetime into an float number of seconds since the epoch -/// ``` pub fn serialize(dt: &DateTime, serializer: S) -> Result where S: ser::Serializer, diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs index 44884649..ea52c88e 100644 --- a/lambda-events/src/custom_serde/headers.rs +++ b/lambda-events/src/custom_serde/headers.rs @@ -5,7 +5,7 @@ use serde::{ }; use std::{borrow::Cow, fmt}; -/// Serialize a http::HeaderMap into a serde str => Vec map +/// Serialize a http::HeaderMap into a serde str => `Vec` map pub(crate) fn serialize_multi_value_headers(headers: &HeaderMap, serializer: S) -> Result where S: Serializer, diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 63f9ac74..32bf9f79 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -/// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use map[string]string, json.RawMessage, interface{}, etc.. +/// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index a1594eb4..df5ba80a 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -2,7 +2,7 @@ //! //! Note that they are similar (but not the same) as the events in the `super` module. //! -//! See https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.custom_resources-readme.html for details. +//! See for details. use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index d99f3c94..30df5a42 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -11,7 +11,7 @@ use serde_json::Value; /// `CloudWatchAlarm` is the generic outer structure of an event triggered by a CloudWatch Alarm. /// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. /// For examples of events that come via CloudWatch Alarms, -/// see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html#Lambda-action-payload +/// see #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarm diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 6384406e..2af343de 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -20,7 +20,7 @@ pub mod tag; pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. -/// For examples of events that come via CloudWatch Events, see https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html +/// For examples of events that come via CloudWatch Events, see #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index d4970f5a..ad7775d2 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -11,7 +11,7 @@ pub type CodeBuildPhaseStatus = String; pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: -/// https://docs.aws.amazon.com/codebuild/latest/userguide/sample-build-notifications.html#sample-build-notifications-ref +/// #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index d51bf8aa..b0a25c5b 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: -/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#acd_event_types +/// #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 22db26b1..6e394c17 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -8,7 +8,7 @@ pub type CodePipelineState = String; pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: -/// https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#codepipeline_event_type +/// #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2a3d7558..2b43f4a9 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -110,7 +110,7 @@ impl fmt::Display for KeyType { } /// The `Event` stream event handled to Lambda -/// http://docs.aws.amazon.com/lambda/latest/dg/eventsources.html#eventsources-ddb-update +/// #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] @@ -118,7 +118,7 @@ pub struct Event { } /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows -/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// ref. #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { @@ -215,8 +215,8 @@ pub struct UserIdentity { #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX - /// epoch time (http://www.epochconverter.com/) format. Might not be present in - /// the record: https://github.com/awslabs/aws-lambda-rust-runtime/issues/889 + /// epoch time () format. Might not be present in + /// the record: #[serde(rename = "ApproximateCreationDateTime")] #[serde(with = "float_unix_epoch")] #[serde(default)] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 7756e0e4..5ed14840 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -5,7 +5,7 @@ use serde_json::Value; /// Parse EventBridge events. /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// -/// See https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events-structure.html for structure details. +/// See for structure details. #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 3835b515..07352120 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -3,7 +3,7 @@ use http::HeaderMap; use serde::{Deserialize, Serialize}; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. -/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +/// See #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { @@ -58,7 +58,7 @@ pub struct IoTCoreConnectionMetadata { } /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. -/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +/// See #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index fac80e07..5557ea6b 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -12,7 +12,7 @@ pub struct KinesisEvent { } /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows -/// ref. https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows +/// ref. #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 738cd72c..1cd7b934 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_headers, serialize_headers}; /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas -/// See: https://docs.aws.amazon.com/AmazonS3/latest/userguide/olap-writing-lambda.html +/// See: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3ObjectLambdaEvent

diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 9dd69f66..563dda1a 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -126,7 +126,7 @@ pub struct SqsApiEvent { pub messages: Vec, } -/// Alternative to SqsApiEvent to be used alongside SqsApiMessageObj when you need to +/// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to /// deserialize a nested object into a struct of type T within the SQS Message rather /// than just using the raw SQS Message string #[serde_with::serde_as] diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index e21cdc13..8165a7c2 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,4 +1,5 @@ #![deny(rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "http")] pub use http; #[cfg(feature = "query_map")] diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index edc9beb5..424050ab 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -5,8 +5,8 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `Window` is the object that captures the time window for the records in the event when using the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Window { @@ -24,8 +24,8 @@ impl Default for Window { } /// `TimeWindowProperties` is the object that captures properties that relate to the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowProperties { @@ -51,8 +51,8 @@ pub struct TimeWindowProperties { } /// `TimeWindowEventResponseProperties` is the object that captures response properties that relate to the tumbling windows feature -/// Kinesis: https://docs.aws.amazon.com/lambda/latest/dg/with-kinesis.html#services-kinesis-windows -/// DDB: https://docs.aws.amazon.com/lambda/latest/dg/with-ddb.html#services-ddb-windows +/// Kinesis: +/// DDB: #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponseProperties { diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index 81c16337..a27635b8 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![allow(clippy::multiple_crate_versions, clippy::type_complexity)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! This module includes utilities to create Lambda Runtime Extensions. //! diff --git a/lambda-extension/src/logs.rs b/lambda-extension/src/logs.rs index c3b0cda2..541dedc2 100644 --- a/lambda-extension/src/logs.rs +++ b/lambda-extension/src/logs.rs @@ -12,7 +12,7 @@ use tracing::{error, trace}; use crate::{Error, ExtensionError}; /// Payload received from the Lambda Logs API -/// See: https://docs.aws.amazon.com/lambda/latest/dg/runtimes-logs-api.html#runtimes-logs-api-msg +/// See: #[derive(Clone, Debug, Deserialize, PartialEq)] pub struct LambdaLog { /// Time when the log was generated diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 92cd5dae..cea99750 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -1,4 +1,5 @@ #![warn(missing_docs, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //#![deny(warnings)] //! Enriches the `lambda` crate with [`http`](https://github.com/hyperium/http) //! types targeting AWS [ALB](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html), [API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/welcome.html) REST and HTTP API lambda integrations. @@ -188,7 +189,7 @@ where /// Runtime APIs](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-api.html). /// /// This takes care of transforming the LambdaEvent into a [`Request`] and then -/// converting the result into a [`LambdaResponse`]. +/// converting the result into a `LambdaResponse`. pub async fn run<'a, R, S, E>(handler: S) -> Result<(), Error> where S: Service, diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index e8528fdf..fa8953f2 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -153,7 +153,7 @@ impl LambdaResponse { /// /// Types that implement this trait can be used as return types for handler functions. pub trait IntoResponse { - /// Transform into a Response Future + /// Transform into a `Response` Future fn into_response(self) -> ResponseFuture; } diff --git a/lambda-runtime-api-client/src/body/channel.rs b/lambda-runtime-api-client/src/body/channel.rs index 27574655..f1e094c3 100644 --- a/lambda-runtime-api-client/src/body/channel.rs +++ b/lambda-runtime-api-client/src/body/channel.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! use std::{ pin::Pin, @@ -31,7 +31,7 @@ impl DecodedLength { } } - /// Converts to an Option representing a Known or Unknown length. + /// Converts to an `Option` representing a Known or Unknown length. pub(crate) fn into_opt(self) -> Option { match self { DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None, diff --git a/lambda-runtime-api-client/src/body/mod.rs b/lambda-runtime-api-client/src/body/mod.rs index 46735682..13bfcaa0 100644 --- a/lambda-runtime-api-client/src/body/mod.rs +++ b/lambda-runtime-api-client/src/body/mod.rs @@ -1,5 +1,5 @@ //! HTTP body utilities. Extracted from Axum under MIT license. -//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +//! use crate::{BoxError, Error}; use bytes::Bytes; diff --git a/lambda-runtime-api-client/src/body/sender.rs b/lambda-runtime-api-client/src/body/sender.rs index 0e008454..14c1d918 100644 --- a/lambda-runtime-api-client/src/body/sender.rs +++ b/lambda-runtime-api-client/src/body/sender.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! use crate::Error; use std::task::{Context, Poll}; diff --git a/lambda-runtime-api-client/src/body/watch.rs b/lambda-runtime-api-client/src/body/watch.rs index a5f8ae41..f31f4f27 100644 --- a/lambda-runtime-api-client/src/body/watch.rs +++ b/lambda-runtime-api-client/src/body/watch.rs @@ -1,5 +1,5 @@ //! Body::channel utilities. Extracted from Hyper under MIT license. -//! https://github.com/hyperium/hyper/blob/master/LICENSE +//! //! An SPSC broadcast channel. //! diff --git a/lambda-runtime-api-client/src/error.rs b/lambda-runtime-api-client/src/error.rs index dbb87b64..d8ff30b2 100644 --- a/lambda-runtime-api-client/src/error.rs +++ b/lambda-runtime-api-client/src/error.rs @@ -1,5 +1,5 @@ //! Extracted from Axum under MIT license. -//! https://github.com/tokio-rs/axum/blob/main/axum/LICENSE +//! use std::{error::Error as StdError, fmt}; pub use tower::BoxError; /// Errors that can happen when using axum. diff --git a/lambda-runtime-api-client/src/lib.rs b/lambda-runtime-api-client/src/lib.rs index 78b51db1..3df616ab 100644 --- a/lambda-runtime-api-client/src/lib.rs +++ b/lambda-runtime-api-client/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] #![allow(clippy::multiple_crate_versions)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! This crate includes a base HTTP client to interact with //! the AWS Lambda Runtime API. diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 79216cf7..097e8dcf 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -42,7 +42,7 @@ pub fn init_default_subscriber() { /// /// You might want to avoid writing to STDOUT in the local context via [`init_default_subscriber()`], if you have a high-throughput Lambdas that involve /// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). -/// In that case, you might prefer to use [tracing_appender::NonBlocking] instead - particularly if your Lambda is fairly long-running and stays warm. +/// In that case, you might prefer to use [tracing_appender::NonBlocking](https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.NonBlocking.html) instead - particularly if your Lambda is fairly long-running and stays warm. /// Though, note that you are then responsible /// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. /// diff --git a/lambda-runtime/src/layers/otel.rs b/lambda-runtime/src/layers/otel.rs index 42b507f8..5e96dfed 100644 --- a/lambda-runtime/src/layers/otel.rs +++ b/lambda-runtime/src/layers/otel.rs @@ -131,7 +131,7 @@ where } /// Represent the possible values for the OpenTelemetry `faas.trigger` attribute. -/// See https://opentelemetry.io/docs/specs/semconv/attributes-registry/faas/ for more details. +/// See for more details. #[derive(Default, Clone, Copy)] #[non_exhaustive] pub enum OpenTelemetryFaasTrigger { diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index 5598d104..fe7f2aa6 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -1,6 +1,7 @@ #![deny(clippy::all, clippy::cargo)] #![allow(clippy::multiple_crate_versions)] #![warn(missing_docs, nonstandard_style, rust_2018_idioms)] +#![cfg_attr(docsrs, feature(doc_cfg))] //! The mechanism available for defining a Lambda function is as follows: //! @@ -131,7 +132,7 @@ where /// /// You can use this future to execute cleanup or flush related logic prior to runtime shutdown. /// -/// This function's returned future must be resolved prior to [lambda_runtime::run()]. +/// This function's returned future must be resolved prior to `lambda_runtime::run()`. /// /// Note that this implicitly also registers and drives a no-op internal extension that subscribes to no events. /// This extension will be named `_lambda-rust-runtime-no-op-graceful-shutdown-helper`. This extension name @@ -141,12 +142,12 @@ where /// registered already, you might prefer to manually construct your own graceful shutdown handling without the dummy extension. /// /// For more information on general AWS Lambda graceful shutdown handling, see: -/// https://github.com/aws-samples/graceful-shutdown-with-aws-lambda +/// /// /// # Panics /// /// This function panics if: -/// - this function is called after [lambda_runtime::run()] +/// - this function is called after `lambda_runtime::run()` /// - this function is called outside of a context that has access to the tokio i/o /// - the no-op extension cannot be registered /// - either signal listener panics [tokio::signal::unix](https://docs.rs/tokio/latest/tokio/signal/unix/fn.signal.html#errors) From 2862581a5be7677afbea9d806ce5f6f05c6d97ce Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Wed, 28 May 2025 11:24:16 +0800 Subject: [PATCH 180/211] Bump version for runtime and runtime-api-client (#999) lambda_runtime 0.14.1 lambda_runtime_api_client 0.12.1 --- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 65c5b130..123dcc17 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.0" +version = "0.12.1" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 7ffca008..ce1d6876 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.0" +version = "0.14.1" authors = [ "David Calavera ", "Harold Sun ", @@ -45,7 +45,7 @@ hyper-util = { workspace = true, features = [ "tokio", ] } lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client", default-features = false } +lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 1a7c6969732f31cbbafe8fd1dec7a96a993bdb24 Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Thu, 29 May 2025 07:33:37 +0800 Subject: [PATCH 181/211] feat: derive Deserialize, Clone, PartialEq, Eq for MetadataPrelude (#956) --- lambda-runtime/src/types.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lambda-runtime/src/types.rs b/lambda-runtime/src/types.rs index ee09978f..5e5f487a 100644 --- a/lambda-runtime/src/types.rs +++ b/lambda-runtime/src/types.rs @@ -176,7 +176,7 @@ impl LambdaEvent { } /// Metadata prelude for a stream response. -#[derive(Debug, Default, Serialize)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct MetadataPrelude { #[serde(with = "http_serde::status_code")] @@ -478,4 +478,22 @@ mod test { let _ = invoke_request_id(&headers); } + + #[test] + fn serde_metadata_prelude() { + let metadata_prelude = MetadataPrelude { + status_code: StatusCode::OK, + headers: { + let mut headers = HeaderMap::new(); + headers.insert("key", "val".parse().unwrap()); + headers + }, + cookies: vec!["cookie".to_string()], + }; + + let serialized = serde_json::to_string(&metadata_prelude).unwrap(); + let deserialized: MetadataPrelude = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(metadata_prelude, deserialized); + } } From 24125e2b2220af017a8ba8886b7d15af2f392e13 Mon Sep 17 00:00:00 2001 From: Raul Escobar <74686538+raulescobar-g@users.noreply.github.com> Date: Thu, 29 May 2025 00:11:42 -0500 Subject: [PATCH 182/211] Add type hint to LambdaRequest's Deserialize impl to avoid compiler recursive loop (#960) --- lambda-http/src/deserializer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 4c0ad519..4a09ff9a 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -20,7 +20,7 @@ impl<'de> Deserialize<'de> for LambdaRequest { where D: serde::Deserializer<'de>, { - let raw_value: Box = Box::deserialize(deserializer)?; + let raw_value: Box = Box::::deserialize(deserializer)?; let data = raw_value.get(); #[cfg(feature = "apigw_rest")] From 34b9c04fd6af13000f213a7b81cfd8b7c1c3d414 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 29 May 2025 20:28:57 -0700 Subject: [PATCH 183/211] fix(docs): enable all features in docs.rs build (#1000) * fix(docs): enable all features in docs.rs build * fix(docs): await the graceful shutdown future in the README example * fix(docs): add missing docs.rs feature tags in various places --- README.md | 2 +- lambda-events/Cargo.toml | 3 ++ lambda-events/src/custom_serde/mod.rs | 1 + lambda-events/src/encodings/mod.rs | 2 ++ lambda-events/src/event/mod.rs | 41 ++++++++++++++++++++++++ lambda-events/src/lib.rs | 45 +++++++++++++++++++++++++++ lambda-extension/Cargo.toml | 3 ++ lambda-http/Cargo.toml | 3 ++ lambda-runtime-api-client/Cargo.toml | 3 ++ lambda-runtime/Cargo.toml | 3 ++ lambda-runtime/src/diagnostic.rs | 3 ++ lambda-runtime/src/layers/mod.rs | 1 + lambda-runtime/src/lib.rs | 2 +- 13 files changed, 110 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 981633c9..89f40f92 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ async fn main() -> Result<(), Error> { let shutdown_hook = || async move { std::mem::drop(log_guard); }; - lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook); + lambda_runtime::spawn_graceful_shutdown_handler(shutdown_hook).await; lambda_runtime::run(func).await?; Ok(()) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 4e9b4f79..b68b94e8 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -124,3 +124,6 @@ sqs = ["serde_with"] streams = [] documentdb = [] eventbridge = ["chrono", "serde_with"] + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/lambda-events/src/custom_serde/mod.rs b/lambda-events/src/custom_serde/mod.rs index 729dee3d..aca3cd6c 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -8,6 +8,7 @@ use std::collections::HashMap; #[cfg(feature = "codebuild")] pub(crate) mod codebuild_time; #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub type CodeBuildNumber = f32; #[cfg(any( diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index 23399664..f7520c30 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -6,10 +6,12 @@ mod time; use crate::custom_serde::{deserialize_base64, serialize_base64}; #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] pub use self::time::*; #[cfg(feature = "http")] mod http; #[cfg(feature = "http")] +#[cfg_attr(docsrs, doc(cfg(feature = "http")))] pub use self::http::*; pub type Error = Box; diff --git a/lambda-events/src/event/mod.rs b/lambda-events/src/event/mod.rs index d63acc4d..275450fd 100644 --- a/lambda-events/src/event/mod.rs +++ b/lambda-events/src/event/mod.rs @@ -1,162 +1,203 @@ /// AWS Lambda event definitions for activemq. #[cfg(feature = "activemq")] +#[cfg_attr(docsrs, doc(cfg(feature = "activemq")))] pub mod activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] +#[cfg_attr(docsrs, doc(cfg(feature = "alb")))] pub mod alb; /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] +#[cfg_attr(docsrs, doc(cfg(feature = "apigw")))] pub mod apigw; /// AWS Lambda event definitions for appsync. #[cfg(feature = "appsync")] +#[cfg_attr(docsrs, doc(cfg(feature = "appsync")))] pub mod appsync; /// AWS Lambda event definitions for autoscaling. #[cfg(feature = "autoscaling")] +#[cfg_attr(docsrs, doc(cfg(feature = "autoscaling")))] pub mod autoscaling; /// AWS Lambda event definitions for agent for amazon bedrock #[cfg(feature = "bedrock_agent_runtime")] +#[cfg_attr(docsrs, doc(cfg(feature = "bedrock_agent_runtime")))] pub mod bedrock_agent_runtime; /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] +#[cfg_attr(docsrs, doc(cfg(feature = "chime_bot")))] pub mod chime_bot; /// AWS Lambda event definitions for clientvpn. #[cfg(feature = "clientvpn")] +#[cfg_attr(docsrs, doc(cfg(feature = "clientvpn")))] pub mod clientvpn; /// AWS Lambda event definitions for cloudformation. #[cfg(feature = "cloudformation")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudformation")))] pub mod cloudformation; /// AWS Lambda event definitions for CloudWatch alarms. #[cfg(feature = "cloudwatch_alarms")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_alarms")))] pub mod cloudwatch_alarms; /// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_events")))] pub mod cloudwatch_events; /// AWS Lambda event definitions for cloudwatch_logs. #[cfg(feature = "cloudwatch_logs")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_logs")))] pub mod cloudwatch_logs; /// AWS Lambda event definitions for code_commit. #[cfg(feature = "code_commit")] +#[cfg_attr(docsrs, doc(cfg(feature = "code_commit")))] pub mod code_commit; /// AWS Lambda event definitions for codebuild. #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub mod codebuild; /// AWS Lambda event definitions for codedeploy. #[cfg(feature = "codedeploy")] +#[cfg_attr(docsrs, doc(cfg(feature = "codedeploy")))] pub mod codedeploy; /// AWS Lambda event definitions for codepipeline_cloudwatch. #[cfg(feature = "codepipeline_cloudwatch")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_cloudwatch")))] pub mod codepipeline_cloudwatch; /// AWS Lambda event definitions for codepipeline_job. #[cfg(feature = "codepipeline_job")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_job")))] pub mod codepipeline_job; /// AWS Lambda event definitions for cognito. #[cfg(feature = "cognito")] +#[cfg_attr(docsrs, doc(cfg(feature = "cognito")))] pub mod cognito; /// AWS Lambda event definitions for config. #[cfg(feature = "config")] +#[cfg_attr(docsrs, doc(cfg(feature = "config")))] pub mod config; /// AWS Lambda event definitions for connect. #[cfg(feature = "connect")] +#[cfg_attr(docsrs, doc(cfg(feature = "connect")))] pub mod connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] +#[cfg_attr(docsrs, doc(cfg(feature = "dynamodb")))] pub mod dynamodb; /// AWS Lambda event definitions for ecr_scan. #[cfg(feature = "ecr_scan")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecr_scan")))] pub mod ecr_scan; /// AWS Lambda event definitions for firehose. #[cfg(feature = "firehose")] +#[cfg_attr(docsrs, doc(cfg(feature = "firehose")))] pub mod firehose; /// AWS Lambda event definitions for iam. #[cfg(feature = "iam")] +#[cfg_attr(docsrs, doc(cfg(feature = "iam")))] pub mod iam; /// AWS Lambda event definitions for iot. #[cfg(feature = "iot")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot")))] pub mod iot; /// AWS Lambda event definitions for iot_1_click. #[cfg(feature = "iot_1_click")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_1_click")))] pub mod iot_1_click; /// AWS Lambda event definitions for iot_button. #[cfg(feature = "iot_button")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_button")))] pub mod iot_button; /// AWS Lambda event definitions for iot_deprecated. #[cfg(feature = "iot_deprecated")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_deprecated")))] pub mod iot_deprecated; /// AWS Lambda event definitions for kafka. #[cfg(feature = "kafka")] +#[cfg_attr(docsrs, doc(cfg(feature = "kafka")))] pub mod kafka; /// AWS Lambda event definitions for kinesis. #[cfg(feature = "kinesis")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis")))] pub mod kinesis; /// AWS Lambda event definitions for lambda_function_urls. #[cfg(feature = "lambda_function_urls")] +#[cfg_attr(docsrs, doc(cfg(feature = "lambda_function_urls")))] pub mod lambda_function_urls; /// AWS Lambda event definitions for lex. #[cfg(feature = "lex")] +#[cfg_attr(docsrs, doc(cfg(feature = "lex")))] pub mod lex; /// AWS Lambda event definitions for rabbitmq. #[cfg(feature = "rabbitmq")] +#[cfg_attr(docsrs, doc(cfg(feature = "rabbitmq")))] pub mod rabbitmq; /// AWS Lambda event definitions for s3. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub mod s3; /// AWS Lambda event definitions for secretsmanager. #[cfg(feature = "secretsmanager")] +#[cfg_attr(docsrs, doc(cfg(feature = "secretsmanager")))] pub mod secretsmanager; /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] +#[cfg_attr(docsrs, doc(cfg(feature = "ses")))] pub mod ses; /// AWS Lambda event definitions for SNS. #[cfg(feature = "sns")] +#[cfg_attr(docsrs, doc(cfg(feature = "sns")))] pub mod sns; /// AWS Lambda event definitions for SQS. #[cfg(feature = "sqs")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqs")))] pub mod sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] +#[cfg_attr(docsrs, doc(cfg(feature = "streams")))] pub mod streams; // AWS Lambda event definitions for DocumentDB #[cfg(feature = "documentdb")] +#[cfg_attr(docsrs, doc(cfg(feature = "documentdb")))] pub mod documentdb; /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] +#[cfg_attr(docsrs, doc(cfg(feature = "eventbridge")))] pub mod eventbridge; diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs index 8165a7c2..d35dbd76 100644 --- a/lambda-events/src/lib.rs +++ b/lambda-events/src/lib.rs @@ -1,14 +1,17 @@ #![deny(rust_2018_idioms)] #![cfg_attr(docsrs, feature(doc_cfg))] #[cfg(feature = "http")] +#[cfg_attr(docsrs, doc(cfg(feature = "http")))] pub use http; #[cfg(feature = "query_map")] +#[cfg_attr(docsrs, doc(cfg(feature = "query_map")))] pub use query_map; mod custom_serde; /// Encodings used in AWS Lambda json event values. pub mod encodings; #[cfg(feature = "chrono")] +#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))] pub mod time_window; /// AWS Lambda event definitions. @@ -16,168 +19,210 @@ pub mod event; /// AWS Lambda event definitions for activemq. #[cfg(feature = "activemq")] +#[cfg_attr(docsrs, doc(cfg(feature = "activemq")))] pub use event::activemq; /// AWS Lambda event definitions for alb. #[cfg(feature = "alb")] +#[cfg_attr(docsrs, doc(cfg(feature = "alb")))] pub use event::alb; /// AWS Lambda event definitions for apigw. #[cfg(feature = "apigw")] +#[cfg_attr(docsrs, doc(cfg(feature = "apigw")))] pub use event::apigw; /// AWS Lambda event definitions for appsync. #[cfg(feature = "appsync")] +#[cfg_attr(docsrs, doc(cfg(feature = "appsync")))] pub use event::appsync; /// AWS Lambda event definitions for autoscaling. #[cfg(feature = "autoscaling")] +#[cfg_attr(docsrs, doc(cfg(feature = "autoscaling")))] pub use event::autoscaling; /// AWS Lambda event definitions for chime_bot. #[cfg(feature = "chime_bot")] +#[cfg_attr(docsrs, doc(cfg(feature = "chime_bot")))] pub use event::chime_bot; /// AWS Lambda event definitions for clientvpn. #[cfg(feature = "clientvpn")] +#[cfg_attr(docsrs, doc(cfg(feature = "clientvpn")))] pub use event::clientvpn; /// AWS Lambda event definitions for cloudformation #[cfg(feature = "cloudformation")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudformation")))] pub use event::cloudformation; /// AWS Lambda event definitions for CloudWatch alarms. #[cfg(feature = "cloudwatch_alarms")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_alarms")))] pub use event::cloudwatch_alarms; /// AWS Lambda event definitions for CloudWatch events. #[cfg(feature = "cloudwatch_events")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_events")))] pub use event::cloudwatch_events; /// AWS Lambda event definitions for cloudwatch_logs. #[cfg(feature = "cloudwatch_logs")] +#[cfg_attr(docsrs, doc(cfg(feature = "cloudwatch_logs")))] pub use event::cloudwatch_logs; /// AWS Lambda event definitions for code_commit. #[cfg(feature = "code_commit")] +#[cfg_attr(docsrs, doc(cfg(feature = "code_commit")))] pub use event::code_commit; /// AWS Lambda event definitions for codebuild. #[cfg(feature = "codebuild")] +#[cfg_attr(docsrs, doc(cfg(feature = "codebuild")))] pub use event::codebuild; /// AWS Lambda event definitions for codedeploy. #[cfg(feature = "codedeploy")] +#[cfg_attr(docsrs, doc(cfg(feature = "codedeploy")))] pub use event::codedeploy; /// AWS Lambda event definitions for codepipeline_cloudwatch. #[cfg(feature = "codepipeline_cloudwatch")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_cloudwatch")))] pub use event::codepipeline_cloudwatch; /// AWS Lambda event definitions for codepipeline_job. #[cfg(feature = "codepipeline_job")] +#[cfg_attr(docsrs, doc(cfg(feature = "codepipeline_job")))] pub use event::codepipeline_job; /// AWS Lambda event definitions for cognito. #[cfg(feature = "cognito")] +#[cfg_attr(docsrs, doc(cfg(feature = "cognito")))] pub use event::cognito; /// AWS Lambda event definitions for config. #[cfg(feature = "config")] +#[cfg_attr(docsrs, doc(cfg(feature = "config")))] pub use event::config; /// AWS Lambda event definitions for connect. #[cfg(feature = "connect")] +#[cfg_attr(docsrs, doc(cfg(feature = "connect")))] pub use event::connect; /// AWS Lambda event definitions for dynamodb. #[cfg(feature = "dynamodb")] +#[cfg_attr(docsrs, doc(cfg(feature = "dynamodb")))] pub use event::dynamodb; /// AWS Lambda event definitions for ecr_scan. #[cfg(feature = "ecr_scan")] +#[cfg_attr(docsrs, doc(cfg(feature = "ecr_scan")))] pub use event::ecr_scan; /// AWS Lambda event definitions for firehose. #[cfg(feature = "firehose")] +#[cfg_attr(docsrs, doc(cfg(feature = "firehose")))] pub use event::firehose; /// AWS Lambda event definitions for iam. #[cfg(feature = "iam")] +#[cfg_attr(docsrs, doc(cfg(feature = "iam")))] pub use event::iam; /// AWS Lambda event definitions for iot. #[cfg(feature = "iot")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot")))] pub use event::iot; /// AWS Lambda event definitions for iot_1_click. #[cfg(feature = "iot_1_click")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_1_click")))] pub use event::iot_1_click; /// AWS Lambda event definitions for iot_button. #[cfg(feature = "iot_button")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_button")))] pub use event::iot_button; /// AWS Lambda event definitions for iot_deprecated. #[cfg(feature = "iot_deprecated")] +#[cfg_attr(docsrs, doc(cfg(feature = "iot_deprecated")))] pub use event::iot_deprecated; /// AWS Lambda event definitions for kafka. #[cfg(feature = "kafka")] +#[cfg_attr(docsrs, doc(cfg(feature = "kafka")))] pub use event::kafka; /// AWS Lambda event definitions for kinesis. #[cfg(feature = "kinesis")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis")))] pub use event::kinesis; /// AWS Lambda event definitions for kinesis_analytics. #[cfg(feature = "kinesis_analytics")] +#[cfg_attr(docsrs, doc(cfg(feature = "kinesis_analytics")))] pub use event::kinesis::analytics as kinesis_analytics; /// AWS Lambda event definitions for lambda_function_urls. #[cfg(feature = "lambda_function_urls")] +#[cfg_attr(docsrs, doc(cfg(feature = "lambda_function_urls")))] pub use event::lambda_function_urls; /// AWS Lambda event definitions for lex. #[cfg(feature = "lex")] +#[cfg_attr(docsrs, doc(cfg(feature = "lex")))] pub use event::lex; /// AWS Lambda event definitions for rabbitmq. #[cfg(feature = "rabbitmq")] +#[cfg_attr(docsrs, doc(cfg(feature = "rabbitmq")))] pub use event::rabbitmq; /// AWS Lambda event definitions for s3. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub use event::s3; /// AWS Lambda event definitions for s3_batch_job. #[cfg(feature = "s3")] +#[cfg_attr(docsrs, doc(cfg(feature = "s3")))] pub use event::s3::batch_job as s3_batch_job; /// AWS Lambda event definitions for secretsmanager. #[cfg(feature = "secretsmanager")] +#[cfg_attr(docsrs, doc(cfg(feature = "secretsmanager")))] pub use event::secretsmanager; /// AWS Lambda event definitions for ses. #[cfg(feature = "ses")] +#[cfg_attr(docsrs, doc(cfg(feature = "ses")))] pub use event::ses; /// AWS Lambda event definitions for SNS. #[cfg(feature = "sns")] +#[cfg_attr(docsrs, doc(cfg(feature = "sns")))] pub use event::sns; /// AWS Lambda event definitions for SQS. #[cfg(feature = "sqs")] +#[cfg_attr(docsrs, doc(cfg(feature = "sqs")))] pub use event::sqs; /// AWS Lambda event definitions for streams. #[cfg(feature = "streams")] +#[cfg_attr(docsrs, doc(cfg(feature = "streams")))] pub use event::streams; /// AWS Lambda event definitions for documentdb. #[cfg(feature = "documentdb")] +#[cfg_attr(docsrs, doc(cfg(feature = "documentdb")))] pub use event::documentdb; /// AWS Lambda event definitions for EventBridge. #[cfg(feature = "eventbridge")] +#[cfg_attr(docsrs, doc(cfg(feature = "eventbridge")))] pub use event::eventbridge; diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index e9ff826b..b9a8e8c7 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -38,3 +38,6 @@ tokio = { version = "1.0", features = [ tokio-stream = "0.1.2" tower = { workspace = true, features = ["make", "util"] } tracing = { version = "0.1", features = ["log"] } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 904d26af..572978ee 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -62,3 +62,6 @@ lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api- log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 123dcc17..d24dac80 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -37,3 +37,6 @@ tower-service = { workspace = true } tokio = { version = "1.0", features = ["io-util"] } tracing = { version = "0.1", features = ["log"], optional = true } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } + +[package.metadata.docs.rs] +all-features = true diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index ce1d6876..06e55b4b 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -80,3 +80,6 @@ idna_adapter = "=1.2.0" lambda_runtime = { path = ".", features = ["tracing", "graceful-shutdown"] } pin-project-lite = { workspace = true } tracing-appender = "0.2" + +[package.metadata.docs.rs] +all-features = true \ No newline at end of file diff --git a/lambda-runtime/src/diagnostic.rs b/lambda-runtime/src/diagnostic.rs index c03ce284..60917e31 100644 --- a/lambda-runtime/src/diagnostic.rs +++ b/lambda-runtime/src/diagnostic.rs @@ -119,6 +119,7 @@ impl From for Diagnostic { } #[cfg(feature = "anyhow")] +#[cfg_attr(docsrs, doc(cfg(feature = "anyhow")))] impl From for Diagnostic { fn from(value: anyhow::Error) -> Diagnostic { Diagnostic { @@ -129,6 +130,7 @@ impl From for Diagnostic { } #[cfg(feature = "eyre")] +#[cfg_attr(docsrs, doc(cfg(feature = "eyre")))] impl From for Diagnostic { fn from(value: eyre::Report) -> Diagnostic { Diagnostic { @@ -139,6 +141,7 @@ impl From for Diagnostic { } #[cfg(feature = "miette")] +#[cfg_attr(docsrs, doc(cfg(feature = "miette")))] impl From for Diagnostic { fn from(value: miette::Report) -> Diagnostic { Diagnostic { diff --git a/lambda-runtime/src/layers/mod.rs b/lambda-runtime/src/layers/mod.rs index 1f07f199..a05b6c67 100644 --- a/lambda-runtime/src/layers/mod.rs +++ b/lambda-runtime/src/layers/mod.rs @@ -14,4 +14,5 @@ pub use trace::TracingLayer; #[cfg(feature = "opentelemetry")] mod otel; #[cfg(feature = "opentelemetry")] +#[cfg_attr(docsrs, doc(cfg(feature = "opentelemetry")))] pub use otel::{OpenTelemetryFaasTrigger, OpenTelemetryLayer}; diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index fe7f2aa6..e1dd408f 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -178,7 +178,7 @@ where /// } /// ``` #[cfg(all(unix, feature = "graceful-shutdown"))] -#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "tokio-rt"))))] +#[cfg_attr(docsrs, doc(cfg(all(unix, feature = "graceful-shutdown"))))] pub async fn spawn_graceful_shutdown_handler(shutdown_hook: impl FnOnce() -> Fut + Send + 'static) where Fut: Future + Send + 'static, From 2b3bcb7f20b58cd1e85597c2896997db0f20d131 Mon Sep 17 00:00:00 2001 From: Harold Sun Date: Sun, 8 Jun 2025 14:05:36 +0800 Subject: [PATCH 184/211] Bump all packages versions to release doc updates (#1002) * Bump all packages versions to release doc updates * bump lambda_runtime version --- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index b68b94e8..481f77ba 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.16.0" +version = "0.16.1" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index b9a8e8c7..4754ffbe 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.12.0" +version = "0.12.1" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 572978ee..38b65a56 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.0" +version = "0.15.1" authors = [ "David Calavera ", "Harold Sun ", @@ -39,7 +39,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.14.0", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.2", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -58,7 +58,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.4.3" axum-extra = { version = "0.9.2", features = ["query"] } -lambda_runtime_api_client = { version = "0.12.0", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index d24dac80..640560e0 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.1" +version = "0.12.2" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 06e55b4b..6950e1ba 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.1" +version = "0.14.2" authors = [ "David Calavera ", "Harold Sun ", @@ -44,8 +44,8 @@ hyper-util = { workspace = true, features = [ "http1", "tokio", ] } -lambda-extension = { version = "0.12.0", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 62a36b04ddfd67000d407d06d0da60f394ca4b36 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Sat, 7 Jun 2025 23:16:57 -0700 Subject: [PATCH 185/211] feat(ci): add cargo-semver-checks workflows (#1003) --- .github/workflows/build-events.yml | 20 ++++++++++++++++++-- .github/workflows/build-extension.yml | 23 +++++++++++++++++++---- .github/workflows/build-runtime.yml | 21 ++++++++++++++++++--- 3 files changed, 55 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 172bcdd8..372375b5 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -22,7 +22,6 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build events uses: ./.github/actions/rust-build with: @@ -34,6 +33,23 @@ jobs: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - - name: Test individual event features run: make check-event-features + semver: + name: semver + needs: [build, check-event-features] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `aws_lambda_events` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: aws_lambda_events + feature-group: default-features + - name: Check `aws_lambda_events` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: aws_lambda_events + feature-group: all-features diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 0f151f43..2daaa40d 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -1,4 +1,4 @@ -name: Check Lambda Runtime +name: Check Lambda Extension on: push: @@ -28,16 +28,31 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build Runtime API Client uses: ./.github/actions/rust-build with: package: lambda_runtime_api_client toolchain: ${{ matrix.toolchain}} - - - name: Build Extensions runtime uses: ./.github/actions/rust-build with: package: lambda-extension toolchain: ${{ matrix.toolchain}} + semver: + name: semver + needs: build-runtime + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `lambda-extension` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda-extension + feature-group: default-features + - name: Check `lambda-extension` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda-extension + feature-group: all-features diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index 8720af17..c5cf2d08 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -27,21 +27,36 @@ jobs: RUST_BACKTRACE: 1 steps: - uses: actions/checkout@v3 - - name: Build Runtime API Client uses: ./.github/actions/rust-build with: package: lambda_runtime_api_client toolchain: ${{ matrix.toolchain}} - - name: Build Functions runtime uses: ./.github/actions/rust-build with: package: lambda_runtime toolchain: ${{ matrix.toolchain}} - - name: Build HTTP layer uses: ./.github/actions/rust-build with: package: lambda_http toolchain: ${{ matrix.toolchain}} + semver: + name: semver + needs: build-runtime + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Check `lambda_runtime_api_client`, `lambda_runtime`, lambda_http` semver with only default features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda_runtime_api_client, lambda_runtime, lambda_http + feature-group: default-features + - name: Check `lambda_runtime_api_client`, `lambda_runtime`, lambda_http` semver with all features + uses: obi1kenobi/cargo-semver-checks-action@v2 + with: + rust-toolchain: stable + package: lambda_runtime_api_client, lambda_runtime, lambda_http + feature-group: all-features From 6747fd3c65c6f6285c82af7b611f680cc6fcd83e Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 17:10:37 +0000 Subject: [PATCH 186/211] fix(ci): cut run-integration-test.yml over to "mlugg/setup-zig@v2" --- .github/workflows/run-integration-test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index a492e50d..d9a196f6 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -21,9 +21,9 @@ jobs: # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed tag: v1.8.1 - name: install Zig toolchain - uses: korandoru/setup-zig@v1 + uses: mlugg/setup-zig@v2 with: - zig-version: 0.10.0 + version: 0.10.0 - name: install SAM uses: aws-actions/setup-sam@v2 with: From a301d72d4bb7120201475239065ae52cb869dfce Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 17:59:33 +0000 Subject: [PATCH 187/211] chore: clippy fixes on inline string formatting --- lambda-events/src/custom_serde/codebuild_time.rs | 4 ++-- lambda-events/src/custom_serde/float_unix_epoch.rs | 7 +++---- lambda-events/src/encodings/time.rs | 8 ++++---- lambda-events/src/event/cloudwatch_alarms/mod.rs | 2 +- lambda-events/src/event/dynamodb/mod.rs | 10 +++++----- lambda-extension/src/extension.rs | 4 ++-- 6 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs index 92b0f796..8d90203f 100644 --- a/lambda-events/src/custom_serde/codebuild_time.rs +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -14,13 +14,13 @@ impl Visitor<'_> for TimeVisitor { type Value = DateTime; fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) + write!(formatter, "valid codebuild time: {CODEBUILD_TIME_FORMAT}") } fn visit_str(self, val: &str) -> Result { NaiveDateTime::parse_from_str(val, CODEBUILD_TIME_FORMAT) .map(|naive| naive.and_utc()) - .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) + .map_err(|e| DeError::custom(format!("Parse error {e} for {val}"))) } } diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs index f4907a1f..164b4c7e 100644 --- a/lambda-events/src/custom_serde/float_unix_epoch.rs +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -14,7 +14,7 @@ fn ne_timestamp(ts: T) -> SerdeError { impl fmt::Debug for SerdeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "ChronoSerdeError({})", self) + write!(f, "ChronoSerdeError({self})") } } @@ -22,7 +22,7 @@ impl fmt::Display for SerdeError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { SerdeError::NonExistent { ref timestamp } => { - write!(f, "value is not a legal timestamp: {}", timestamp) + write!(f, "value is not a legal timestamp: {timestamp}") } SerdeError::Ambiguous { ref timestamp, @@ -30,8 +30,7 @@ impl fmt::Display for SerdeError { ref max, } => write!( f, - "value is an ambiguous timestamp: {}, could be either of {}, {}", - timestamp, min, max + "value is an ambiguous timestamp: {timestamp}, could be either of {min}, {max}", ), } } diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index d4903360..df22ef24 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -125,7 +125,7 @@ where let whole_seconds = seconds + (milliseconds as i64 / 1000); let subsec_millis = milliseconds % 1000; if milliseconds > 0 { - let combined = format!("{}.{:03}", whole_seconds, subsec_millis); + let combined = format!("{whole_seconds}.{subsec_millis:03}"); serializer.serialize_str(&combined) } else { serializer.serialize_str(&whole_seconds.to_string()) @@ -159,7 +159,7 @@ where { let seconds = f64::deserialize(deserializer)?; TimeDelta::try_seconds(seconds as i64) - .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{}`", seconds))) + .ok_or_else(|| D::Error::custom(format!("invalid time delta seconds `{seconds}`"))) } fn serialize_duration_minutes(duration: &TimeDelta, serializer: S) -> Result @@ -177,7 +177,7 @@ where { let minutes = f64::deserialize(deserializer)?; TimeDelta::try_minutes(minutes as i64) - .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{}`", minutes))) + .ok_or_else(|| D::Error::custom(format!("invalid time delta minutes `{minutes}`"))) } fn normalize_timestamp<'de, D>(deserializer: D) -> Result<(u64, u64), D::Error> @@ -199,7 +199,7 @@ where }; // We need to do this due to floating point issues. - let input_as_string = format!("{}", input); + let input_as_string = input.to_string(); let parts: Result, _> = input_as_string .split('.') .map(|x| x.parse::().map_err(DeError::custom)) diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 30df5a42..720236c2 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -224,7 +224,7 @@ impl Serialize for CloudWatchAlarmStateReasonData { Self::Composite(m) => serde_json::to_string(m), Self::Generic(m) => serde_json::to_string(m), }; - let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {}", e)))?; + let s = r.map_err(|e| SerError::custom(format!("failed to serialize struct as string {e}")))?; serializer.serialize_str(&s) } diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2b43f4a9..91380f82 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -27,7 +27,7 @@ impl fmt::Display for StreamViewType { StreamViewType::NewAndOldImages => "NEW_AND_OLD_IMAGES", StreamViewType::KeysOnly => "KEYS_ONLY", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -48,7 +48,7 @@ impl fmt::Display for StreamStatus { StreamStatus::Disabling => "DISABLING", StreamStatus::Disabled => "DISABLED", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -69,7 +69,7 @@ impl fmt::Display for SharedIteratorType { SharedIteratorType::AtSequenceNumber => "AT_SEQUENCE_NUMBER", SharedIteratorType::AfterSequenceNumber => "AFTER_SEQUENCE_NUMBER", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -88,7 +88,7 @@ impl fmt::Display for OperationType { OperationType::Modify => "MODIFY", OperationType::Remove => "REMOVE", }; - write!(f, "{}", val) + write!(f, "{val}") } } @@ -105,7 +105,7 @@ impl fmt::Display for KeyType { KeyType::Hash => "HASH", KeyType::Range => "RANGE", }; - write!(f, "{}", val) + write!(f, "{val}") } } diff --git a/lambda-extension/src/extension.rs b/lambda-extension/src/extension.rs index 15e0befd..e7d83847 100644 --- a/lambda-extension/src/extension.rs +++ b/lambda-extension/src/extension.rs @@ -259,7 +259,7 @@ where let io = TokioIo::new(tcp); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { - println!("Error serving connection: {:?}", err); + println!("Error serving connection: {err:?}"); } }); } @@ -305,7 +305,7 @@ where let io = TokioIo::new(tcp); tokio::task::spawn(async move { if let Err(err) = http1::Builder::new().serve_connection(io, make_service).await { - println!("Error serving connection: {:?}", err); + println!("Error serving connection: {err:?}"); } }); } From 2278cea4134c9ca4529c6ef05d8768e9f4caec11 Mon Sep 17 00:00:00 2001 From: jlizen Date: Mon, 30 Jun 2025 19:26:38 +0000 Subject: [PATCH 188/211] chore(ci): update TODO link for pinned cargo-lambda --- .github/workflows/run-integration-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index d9a196f6..03065d9e 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,7 +18,7 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 - # TODO: unpin once https://github.com/cargo-lambda/cargo-lambda/issues/856 is fixed + # TODO: unpin once https://github.com/awslabs/aws-lambda-rust-runtime/issues/1006 is fixed tag: v1.8.1 - name: install Zig toolchain uses: mlugg/setup-zig@v2 From f8b9a2acf472c7c6b23aa13aaded85bef53ae1ea Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Mon, 7 Jul 2025 07:39:07 -0700 Subject: [PATCH 189/211] chore(lambda-runtime): slightly optimize graceful shutdown helper by using new tokio::try_join biased; api (#1007) --- examples/extension-internal-flush/Cargo.toml | 2 +- examples/extension-internal-flush/src/main.rs | 6 ++++-- lambda-runtime/Cargo.toml | 2 +- lambda-runtime/src/lib.rs | 8 +++++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml index daadd0eb..70e27ebf 100644 --- a/examples/extension-internal-flush/Cargo.toml +++ b/examples/extension-internal-flush/Cargo.toml @@ -9,4 +9,4 @@ aws_lambda_events = { path = "../../lambda-events" } lambda-extension = { path = "../../lambda-extension" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" -tokio = { version = "1", features = ["macros", "sync"] } +tokio = { version = "1.46", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index c728030b..9a515ff8 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -101,9 +101,11 @@ async fn main() -> Result<(), Error> { let handler = Arc::new(EventHandler::new(request_done_sender)); - // TODO: add biased! to always poll the handler future first, once supported: - // https://github.com/tokio-rs/tokio/issues/7304 tokio::try_join!( + // always poll the handler function first before the flush extension, + // this results in a smaller future due to not needing to track which was polled first + // each time, and also a tiny latency savings + biased; lambda_runtime::run(service_fn(|event| { let handler = handler.clone(); async move { handler.invoke(event).await } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6950e1ba..c4787d56 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -52,7 +52,7 @@ pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" serde_path_to_error = "0.1.11" -tokio = { version = "1.0", features = [ +tokio = { version = "1.46", features = [ "macros", "io-util", "sync", diff --git a/lambda-runtime/src/lib.rs b/lambda-runtime/src/lib.rs index e1dd408f..cbcd0a9e 100644 --- a/lambda-runtime/src/lib.rs +++ b/lambda-runtime/src/lib.rs @@ -223,9 +223,11 @@ where } }; - // TODO: add biased! to always poll the signal handling future first, once supported: - // https://github.com/tokio-rs/tokio/issues/7304 - let _: (_, ()) = tokio::join!(graceful_shutdown_future, async { + let _: (_, ()) = tokio::join!( + // we always poll the graceful shutdown future first, + // which results in a smaller future due to lack of bookkeeping of which was last polled + biased; + graceful_shutdown_future, async { // we suppress extension errors because we don't actually mind if it crashes, // all we need to do is kick off the run so that lambda exits the init phase let _ = extension.run().await; From 03833014cef303dab64bf9585c370bd382ce14c6 Mon Sep 17 00:00:00 2001 From: Spencer Stolworthy Date: Thu, 10 Jul 2025 08:08:20 -0700 Subject: [PATCH 190/211] fix(lambda-events): derive Default on KinesisEvent (#1008) --- lambda-events/src/event/kinesis/event.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 5557ea6b..97f4b708 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -4,7 +4,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] @@ -109,4 +109,11 @@ mod test { let reparsed: KinesisEvent = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "kinesis")] + fn default_kinesis_event() { + let event = KinesisEvent::default(); + assert_eq!(event.records, vec![]); + } } From b927092162a88267a9ac6efb57f3f2cc624ef117 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Thu, 10 Jul 2025 17:20:27 +0200 Subject: [PATCH 191/211] Update all dependencies and fix examples (#959) * add copy/clone trait impl * update all deps and fix examples * remove unused deps * add feature flag for aws-config * update axum * revert derive * update * remove ';' * use aws_config::load_from_env().await; * remove comments * revert * rev * rev * bump ex * update * update ureq * space * fmt * clippy * update otel * fmt --- .../Cargo.toml | 8 ++--- .../src/main.rs | 4 +-- .../consumer/Cargo.toml | 4 +-- .../pizza_lib/Cargo.toml | 2 +- .../producer/Cargo.toml | 9 +++-- .../producer/src/main.rs | 2 +- .../Cargo.toml | 2 -- .../src/main.rs | 4 +-- .../Cargo.toml | 4 +-- .../Cargo.toml | 2 +- .../src/main.rs | 2 +- examples/basic-error-handling/Cargo.toml | 6 ++-- examples/basic-error-handling/src/main.rs | 8 ++--- examples/basic-error-thiserror/Cargo.toml | 2 +- examples/basic-error-thiserror/src/main.rs | 3 +- .../basic-lambda-external-runtime/Cargo.toml | 11 +++--- .../basic-lambda-external-runtime/src/main.rs | 4 +-- examples/basic-lambda/Cargo.toml | 5 +-- examples/basic-lambda/src/main.rs | 2 +- .../Cargo.toml | 19 +++++----- .../src/main.rs | 27 ++++++-------- .../src/s3.rs | 32 ++++++++++------- examples/basic-s3-thumbnail/Cargo.toml | 14 +++----- examples/basic-s3-thumbnail/src/main.rs | 29 ++++++--------- examples/basic-s3-thumbnail/src/s3.rs | 6 ++-- examples/basic-sdk/Cargo.toml | 9 +++-- examples/basic-sdk/src/main.rs | 7 ++-- examples/basic-shared-resource/Cargo.toml | 2 +- examples/basic-sqs/Cargo.toml | 2 +- examples/basic-streaming-response/src/main.rs | 2 +- examples/extension-basic/Cargo.toml | 1 - examples/extension-combined/Cargo.toml | 1 - examples/extension-custom-events/Cargo.toml | 1 - examples/extension-custom-service/Cargo.toml | 1 - examples/extension-internal-flush/Cargo.toml | 2 +- examples/extension-internal-flush/src/main.rs | 6 ++-- examples/extension-logs-basic/Cargo.toml | 1 - .../extension-logs-custom-service/Cargo.toml | 1 - .../Cargo.toml | 6 ++-- .../src/main.rs | 9 +++-- examples/extension-telemetry-basic/Cargo.toml | 1 - .../http-axum-apigw-authorizer/Cargo.toml | 4 +-- .../http-axum-apigw-authorizer/src/main.rs | 3 -- examples/http-axum-diesel-ssl/Cargo.toml | 22 ++++++------ examples/http-axum-diesel-ssl/src/main.rs | 35 +++++++++---------- examples/http-axum-diesel/Cargo.toml | 10 +++--- examples/http-axum-diesel/src/main.rs | 8 +++-- examples/http-axum-middleware/Cargo.toml | 3 +- examples/http-axum-middleware/src/main.rs | 3 +- examples/http-axum/Cargo.toml | 5 ++- examples/http-axum/src/main.rs | 5 ++- examples/http-basic-lambda/Cargo.toml | 1 - examples/http-cors/Cargo.toml | 3 +- examples/http-cors/src/main.rs | 2 +- examples/http-dynamodb/Cargo.toml | 13 ++++--- examples/http-dynamodb/src/main.rs | 23 ++++-------- examples/http-query-parameters/Cargo.toml | 1 - examples/http-query-parameters/src/main.rs | 2 +- examples/http-raw-path/Cargo.toml | 1 - examples/http-shared-resource/Cargo.toml | 1 - examples/http-tower-trace/Cargo.toml | 3 +- examples/http-tower-trace/src/main.rs | 9 +++-- examples/lambda-rds-iam-auth/Cargo.toml | 14 ++++---- examples/lambda-rds-iam-auth/src/main.rs | 35 +++++++------------ examples/opentelemetry-tracing/Cargo.toml | 10 +++--- examples/opentelemetry-tracing/src/main.rs | 10 +++--- lambda-extension/Cargo.toml | 1 - lambda-http/Cargo.toml | 6 ++-- lambda-integration-tests/Cargo.toml | 1 - lambda-runtime-api-client/Cargo.toml | 2 -- lambda-runtime/Cargo.toml | 8 ----- 71 files changed, 211 insertions(+), 296 deletions(-) diff --git a/examples/advanced-appconfig-feature-flags/Cargo.toml b/examples/advanced-appconfig-feature-flags/Cargo.toml index 52ebb843..51b708ec 100644 --- a/examples/advanced-appconfig-feature-flags/Cargo.toml +++ b/examples/advanced-appconfig-feature-flags/Cargo.toml @@ -15,10 +15,10 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -async-trait = "0.1.68" -lambda_runtime = "0.13" -reqwest = { version = "0.11", default-features = false, features = ["json", "rustls-tls"] } +async-trait = "0.1.88" +lambda_runtime = { path = "../../lambda-runtime" } +reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -thiserror = "1.0" +thiserror = "2.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/advanced-appconfig-feature-flags/src/main.rs b/examples/advanced-appconfig-feature-flags/src/main.rs index b7d5e515..87ec54fa 100644 --- a/examples/advanced-appconfig-feature-flags/src/main.rs +++ b/examples/advanced-appconfig-feature-flags/src/main.rs @@ -35,9 +35,9 @@ async fn function_handler( // Use the feature flag let msg = if config.spanish_response { - format!("{}, in spanish.", quote) + format!("{quote}, in spanish.") } else { - format!("{}.", quote) + format!("{quote}.") }; // Return `Response` (it will be serialized to JSON automatically by the runtime) diff --git a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml index 69ec04a0..e82dc1d3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/consumer/Cargo.toml @@ -5,9 +5,7 @@ edition = "2021" [dependencies] #aws dependencies -aws-sdk-config = "0.35.0" -aws-sdk-sqs = "0.35.0" -aws_lambda_events = { version = "0.11.1", features = ["sqs"], default-features = false } +aws_lambda_events = { path = "../../../lambda-events", features = ["sqs"], default-features = false } #lambda runtime lambda_runtime = { path = "../../../lambda-runtime" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml index 76631bbd..2dd69db1 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/pizza_lib/Cargo.toml @@ -4,4 +4,4 @@ version = "0.1.0" edition = "2021" [dependencies] -serde = { version = "1.0.191", features = ["derive"] } +serde = { version = "1.0.219", features = ["derive"] } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml index 83aa48ab..2772f650 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/Cargo.toml @@ -8,14 +8,13 @@ env = { "QUEUE_URL" = "https://changeMe" } [dependencies] #aws dependencies -aws-config = "0.57.1" -aws-sdk-config = "0.35.0" -aws-sdk-sqs = "0.35.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-sqs = "1.74.0" #lambda runtime lambda_runtime = { path = "../../../lambda-runtime" } -serde_json = "1.0.108" +serde_json = "1.0.140" tokio = { version = "1", features = ["macros"] } #shared lib -pizza_lib = { path = "../pizza_lib" } \ No newline at end of file +pizza_lib = { path = "../pizza_lib" } diff --git a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs index 2a70dce3..6a2883f3 100644 --- a/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs +++ b/examples/advanced-sqs-multiple-functions-shared-data/producer/src/main.rs @@ -20,7 +20,7 @@ async fn main() -> Result<(), Error> { // read the queue url from the environment let queue_url = std::env::var("QUEUE_URL").expect("could not read QUEUE_URL"); // build the config from environment variables (fed by AWS Lambda) - let config = aws_config::from_env().load().await; + let config = aws_config::load_from_env().await; // create our SQS Manager let sqs_manager = SQSManager::new(aws_sdk_sqs::Client::new(&config), queue_url); let sqs_manager_ref = &sqs_manager; diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 95050b9a..f02e4efb 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -5,8 +5,6 @@ edition = "2021" [dependencies] serde = "^1" -serde_derive = "^1" -serde_with = { version = "^2", features = ["json"], optional = true } serde_json = "^1" aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index be4eb66f..6cea2f93 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -55,7 +55,7 @@ where D: DeserializeOwned, R: Future>, { - run(service_fn(|e| batch_handler(|d| f(d), e))).await + run(service_fn(|e| batch_handler(&f, e))).await } /// Helper function to lift the user provided `f` function from message to batch of messages. @@ -123,7 +123,7 @@ mod test { } #[tokio::test] - async fn test() -> () { + async fn test() { let msg_to_fail: SqsMessageObj = serde_json::from_str( r#"{ "messageId": "1", diff --git a/examples/basic-cognito-post-confirmation/Cargo.toml b/examples/basic-cognito-post-confirmation/Cargo.toml index 7d2e7ab4..93369e51 100644 --- a/examples/basic-cognito-post-confirmation/Cargo.toml +++ b/examples/basic-cognito-post-confirmation/Cargo.toml @@ -15,8 +15,8 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws-config = "1.5.0" -aws-sdk-ses = "1.28.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-ses = "1.77.0" aws_lambda_events = { path = "../../lambda-events", default-features = false, features = ["cognito"] } lambda_runtime = { path = "../../lambda-runtime" } diff --git a/examples/basic-error-error-crates-integration/Cargo.toml b/examples/basic-error-error-crates-integration/Cargo.toml index 741ec713..24fbc8dc 100644 --- a/examples/basic-error-error-crates-integration/Cargo.toml +++ b/examples/basic-error-error-crates-integration/Cargo.toml @@ -7,6 +7,6 @@ edition = "2021" anyhow = "1" eyre = "0.6.12" lambda_runtime = { path = "../../lambda-runtime", features = ["anyhow", "eyre", "miette"] } -miette = "7.2.0" +miette = "7.6.0" serde = "1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-error-crates-integration/src/main.rs b/examples/basic-error-error-crates-integration/src/main.rs index f4048584..176bd54b 100644 --- a/examples/basic-error-error-crates-integration/src/main.rs +++ b/examples/basic-error-error-crates-integration/src/main.rs @@ -28,7 +28,7 @@ fn miette_error() -> miette::Result<()> { /// Transform an anyhow::Error, eyre::Report, or miette::Report into a lambda_runtime::Diagnostic. /// It does it by enabling the feature `anyhow`, `eyre` or `miette` in the runtime dependency. -/// Those features enable the implementation of `From for Diagnostic` +/// Those features enable the implementation of `From for Diagnostic` /// for `anyhow::Error`, `eyre::Report`, and `miette::Report`. async fn function_handler(event: LambdaEvent) -> Result<(), Diagnostic> { match event.payload.error_type { diff --git a/examples/basic-error-handling/Cargo.toml b/examples/basic-error-handling/Cargo.toml index 1039a139..a0267f97 100644 --- a/examples/basic-error-handling/Cargo.toml +++ b/examples/basic-error-handling/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" -serde_json = "1.0.81" -simple-error = "0.2.3" +serde = "1.0.219" +serde_json = "1.0.140" +simple-error = "0.3.1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-handling/src/main.rs b/examples/basic-error-handling/src/main.rs index 12c954a9..85e97428 100644 --- a/examples/basic-error-handling/src/main.rs +++ b/examples/basic-error-handling/src/main.rs @@ -43,7 +43,7 @@ impl std::fmt::Display for CustomError { /// Display the error struct as a JSON string fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let err_as_json = json!(self).to_string(); - write!(f, "{}", err_as_json) + write!(f, "{err_as_json}") } } @@ -66,7 +66,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result match event.event_type { EventType::SimpleError => { // generate a simple text message error using `simple_error` crate - return Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))); + Err(Box::new(simple_error::SimpleError::new("A simple error as requested!"))) } EventType::CustomError => { // generate a custom error using our own structure @@ -75,7 +75,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result req_id: ctx.request_id, msg: "A custom error as requested!".into(), }; - return Err(Box::new(cust_err)); + Err(Box::new(cust_err)) } EventType::ExternalError => { // try to open a non-existent file to get an error and propagate it with `?` @@ -94,7 +94,7 @@ pub(crate) async fn func(event: LambdaEvent) -> Result msg: "OK".into(), }; - return Ok(resp); + Ok(resp) } } } diff --git a/examples/basic-error-thiserror/Cargo.toml b/examples/basic-error-thiserror/Cargo.toml index d7c7d725..f2b0b449 100644 --- a/examples/basic-error-thiserror/Cargo.toml +++ b/examples/basic-error-thiserror/Cargo.toml @@ -18,5 +18,5 @@ edition = "2021" lambda_runtime = { path = "../../lambda-runtime" } serde = "1" -thiserror = "1.0.61" +thiserror = "2.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-error-thiserror/src/main.rs b/examples/basic-error-thiserror/src/main.rs index 403309bf..2c01b833 100644 --- a/examples/basic-error-thiserror/src/main.rs +++ b/examples/basic-error-thiserror/src/main.rs @@ -1,6 +1,5 @@ use lambda_runtime::{service_fn, Diagnostic, Error, LambdaEvent}; use serde::Deserialize; -use thiserror; #[derive(Deserialize)] struct Request {} @@ -21,7 +20,7 @@ impl From for Diagnostic { }; Diagnostic { error_type: error_type.into(), - error_message: error_message.into(), + error_message, } } } diff --git a/examples/basic-lambda-external-runtime/Cargo.toml b/examples/basic-lambda-external-runtime/Cargo.toml index d6d023d8..40f24d81 100644 --- a/examples/basic-lambda-external-runtime/Cargo.toml +++ b/examples/basic-lambda-external-runtime/Cargo.toml @@ -4,11 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -async-channel = "1.8.0" -futures-lite = "1.13.0" +async-channel = "2.5.0" +futures-lite = "2.6.0" lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.163" -tokio = "1.28.2" - -[dev-dependencies] -tokio-test = "0.4.2" +serde = "1.0.219" +tokio = "1.46.1" diff --git a/examples/basic-lambda-external-runtime/src/main.rs b/examples/basic-lambda-external-runtime/src/main.rs index bd3b4e6c..87891ebc 100644 --- a/examples/basic-lambda-external-runtime/src/main.rs +++ b/examples/basic-lambda-external-runtime/src/main.rs @@ -53,7 +53,7 @@ fn main() -> Result<(), io::Error> { my_runtime(move || future::block_on(app_runtime_task(lambda_rx.clone(), shutdown_tx.clone()))); // Block the main thread until a shutdown signal is received. - future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err))) + future::block_on(shutdown_rx.recv()).map_err(|err| io::Error::other(format!("{err:?}"))) } pub(crate) async fn my_handler(event: LambdaEvent) -> Result { @@ -63,7 +63,7 @@ pub(crate) async fn my_handler(event: LambdaEvent) -> Result) -> Result, _size: u32) -> Vec { mod tests { use super::*; use async_trait::async_trait; - use aws_lambda_events::s3::object_lambda::Configuration; - use aws_lambda_events::s3::object_lambda::HeadObjectContext; - use aws_lambda_events::s3::object_lambda::ListObjectsContext; - use aws_lambda_events::s3::object_lambda::ListObjectsV2Context; - use aws_lambda_events::s3::object_lambda::UserIdentity; - use aws_lambda_events::s3::object_lambda::UserRequest; - use aws_lambda_events::serde_json::json; + use aws_lambda_events::s3::object_lambda::{ + Configuration, HeadObjectContext, ListObjectsContext, ListObjectsV2Context, UserIdentity, UserRequest, + }; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; - use s3::GetFile; - use s3::SendFile; + use s3::{GetFile, SendFile}; + use serde_json::json; #[tokio::test] async fn response_is_good() { mock! { FakeS3Client {} - #[async_trait] impl GetFile for FakeS3Client { - pub fn get_file(&self, url: String) -> Result, Box>; + fn get_file(&self, url: String) -> Result, Box>; } #[async_trait] impl SendFile for FakeS3Client { - pub async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; + async fn send_file(&self, route: String, token: String, vec: Vec) -> Result>; } } @@ -118,9 +113,7 @@ mod tests { .returning(|_1| Ok("IMAGE".into())); mock.expect_send_file() - .withf(|r, t, by| { - return r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes(); - }) + .withf(|r, t, by| r.eq("O_ROUTE") && t.eq("O_TOKEN") && by == "THUMBNAIL".as_bytes()) .returning(|_1, _2, _3| Ok("File sent.".to_string())); let payload = get_s3_event(); @@ -133,7 +126,7 @@ mod tests { } fn get_s3_event() -> S3ObjectLambdaEvent { - return S3ObjectLambdaEvent { + S3ObjectLambdaEvent { x_amz_request_id: ("ID".to_string()), head_object_context: (Some(HeadObjectContext::default())), list_objects_context: (Some(ListObjectsContext::default())), @@ -151,6 +144,6 @@ mod tests { supporting_access_point_arn: ("SAPRN".to_string()), payload: (json!(null)), }), - }; + } } } diff --git a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs index daba3739..69b46ec2 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/s3.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/s3.rs @@ -1,6 +1,8 @@ use async_trait::async_trait; -use aws_sdk_s3::{operation::write_get_object_response::WriteGetObjectResponseError, Client as S3Client}; -use aws_smithy_http::{byte_stream::ByteStream, result::SdkError}; +use aws_sdk_s3::{ + error::SdkError, operation::write_get_object_response::WriteGetObjectResponseError, primitives::ByteStream, + Client as S3Client, +}; use lambda_runtime::tracing; use std::{error, io::Read}; @@ -17,12 +19,17 @@ impl GetFile for S3Client { fn get_file(&self, url: String) -> Result, Box> { tracing::info!("get file url {}", url); - let resp = ureq::get(&url).call()?; - let len: usize = resp.header("Content-Length").unwrap().parse()?; + let mut res = ureq::get(&url).call()?; + let len: usize = res + .headers() + .get("Content-Length") + .and_then(|h| h.to_str().ok()) + .and_then(|s| s.parse().ok()) + .unwrap(); let mut bytes: Vec = Vec::with_capacity(len); - std::io::Read::take(resp.into_reader(), 10_000_000).read_to_end(&mut bytes)?; + std::io::Read::take(res.body_mut().as_reader(), 10_000_000).read_to_end(&mut bytes)?; tracing::info!("got {} bytes", bytes.len()); @@ -46,9 +53,8 @@ impl SendFile for S3Client { .send() .await; - if write.is_err() { - let sdk_error = write.err().unwrap(); - check_error(sdk_error); + if let Err(err) = write { + check_error(err); Err("WriteGetObjectResponse creation error".into()) } else { Ok("File sent.".to_string()) @@ -65,16 +71,16 @@ fn check_error(error: SdkError) { tracing::info!("DispatchFailure"); if err.is_io() { tracing::info!("IO error"); - }; + } if err.is_timeout() { tracing::info!("Timeout error"); - }; + } if err.is_user() { tracing::info!("User error"); - }; - if err.is_other().is_some() { + } + if err.is_other() { tracing::info!("Other error"); - }; + } } SdkError::ResponseError(_err) => tracing::info!("ResponseError"), SdkError::TimeoutError(_err) => tracing::info!("TimeoutError"), diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index 4b9ef3da..fdbd79be 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -17,17 +17,13 @@ edition = "2021" [dependencies] aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1" tokio = { version = "1", features = ["macros"] } -aws-config = "0.55" -aws-smithy-http = "0.55.3" -aws-sdk-s3 = "0.28" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.96.0" thumbnailer = "0.5.1" -mime = "0.3.16" -async-trait = "0.1.68" -webp = "=0.2.1" +mime = "0.3.17" +async-trait = "0.1.88" [dev-dependencies] -mockall = "0.11" -tokio-test = "0.4" +mockall = "0.13.1" chrono = "0.4" diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index 3eb5bfe9..d09da116 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -139,16 +139,11 @@ mod tests { use super::*; use async_trait::async_trait; //use aws_lambda_events::chrono::DateTime; - use aws_lambda_events::s3::S3Bucket; - use aws_lambda_events::s3::S3Entity; - use aws_lambda_events::s3::S3Object; - use aws_lambda_events::s3::S3RequestParameters; - use aws_lambda_events::s3::S3UserIdentity; + use aws_lambda_events::s3::{S3Bucket, S3Entity, S3Object, S3RequestParameters, S3UserIdentity}; use aws_sdk_s3::operation::get_object::GetObjectError; use lambda_runtime::{Context, LambdaEvent}; use mockall::mock; - use s3::GetFile; - use s3::PutFile; + use s3::{GetFile, PutFile}; #[tokio::test] async fn response_is_good() { @@ -163,11 +158,11 @@ mod tests { #[async_trait] impl GetFile for FakeS3Client { - pub async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; + async fn get_file(&self, bucket: &str, key: &str) -> Result, GetObjectError>; } #[async_trait] impl PutFile for FakeS3Client { - pub async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; + async fn put_file(&self, bucket: &str, key: &str, bytes: Vec) -> Result; } } @@ -178,23 +173,21 @@ mod tests { .returning(|_1, _2| Ok("IMAGE".into())); mock.expect_put_file() - .withf(|bu, ke, by| { - return bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes()); - }) + .withf(|bu, ke, by| bu.eq("test-bucket-thumbs") && ke.eq(key) && by.eq("THUMBNAIL".as_bytes())) .return_const(Ok("Done".to_string())); let payload = get_s3_event("ObjectCreated", bucket, key); let event = LambdaEvent { payload, context }; - let result = function_handler(event, 10, &mock).await.unwrap(); + function_handler(event, 10, &mock).await.unwrap(); - assert_eq!((), result); + assert_eq!((), ()); } fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { - return S3Event { + S3Event { records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), - }; + } } fn get_s3_event_record(event_name: &str, bucket_name: &str, object_key: &str) -> S3EventRecord { @@ -218,7 +211,7 @@ mod tests { }), }; - return S3EventRecord { + S3EventRecord { event_version: (Some(String::default())), event_source: (Some(String::default())), aws_region: (Some(String::default())), @@ -232,6 +225,6 @@ mod tests { }), response_elements: (HashMap::new()), s3: (s3_entity), - }; + } } } diff --git a/examples/basic-s3-thumbnail/src/s3.rs b/examples/basic-s3-thumbnail/src/s3.rs index 0dd8629d..1a759371 100644 --- a/examples/basic-s3-thumbnail/src/s3.rs +++ b/examples/basic-s3-thumbnail/src/s3.rs @@ -1,7 +1,5 @@ use async_trait::async_trait; -use aws_sdk_s3::operation::get_object::GetObjectError; -use aws_sdk_s3::Client as S3Client; -use aws_smithy_http::byte_stream::ByteStream; +use aws_sdk_s3::{operation::get_object::GetObjectError, primitives::ByteStream, Client as S3Client}; use lambda_runtime::tracing; #[async_trait] @@ -45,7 +43,7 @@ impl PutFile for S3Client { let result = self.put_object().bucket(bucket).key(key).body(bytes).send().await; match result { - Ok(_) => Ok(format!("Uploaded a file with key {} into {}", key, bucket)), + Ok(_) => Ok(format!("Uploaded a file with key {key} into {bucket}")), Err(err) => Err(err.into_service_error().meta().message().unwrap().to_string()), } } diff --git a/examples/basic-sdk/Cargo.toml b/examples/basic-sdk/Cargo.toml index 454a970f..6e680aaf 100644 --- a/examples/basic-sdk/Cargo.toml +++ b/examples/basic-sdk/Cargo.toml @@ -7,12 +7,11 @@ edition = "2021" [dependencies] async-trait = "0.1" -aws-config = "0.54" -aws-sdk-s3 = "0.24" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.96.0" lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } [dev-dependencies] -mockall = "0.11.3" -tokio-test = "0.4.2" \ No newline at end of file +mockall = "0.13.1" diff --git a/examples/basic-sdk/src/main.rs b/examples/basic-sdk/src/main.rs index d49c84e1..a021f4c2 100644 --- a/examples/basic-sdk/src/main.rs +++ b/examples/basic-sdk/src/main.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use aws_sdk_s3::{output::ListObjectsV2Output, Client as S3Client}; +use aws_sdk_s3::{operation::list_objects_v2::ListObjectsV2Output, Client as S3Client}; use lambda_runtime::{service_fn, tracing, Error, LambdaEvent}; use serde::{Deserialize, Serialize}; @@ -52,8 +52,7 @@ async fn my_handler(event: LambdaEvent, client: &T) -> let objects_rsp = client.list_objects(&bucket).await?; let objects: Vec<_> = objects_rsp .contents() - .ok_or("missing objects in list-objects-v2 response")? - .into_iter() + .iter() .filter_map(|o| o.key().map(|k| k.to_string())) .collect(); @@ -71,7 +70,7 @@ async fn my_handler(event: LambdaEvent, client: &T) -> #[cfg(test)] mod tests { use super::*; - use aws_sdk_s3::model::Object; + use aws_sdk_s3::types::Object; use lambda_runtime::{Context, LambdaEvent}; use mockall::predicate::eq; diff --git a/examples/basic-shared-resource/Cargo.toml b/examples/basic-shared-resource/Cargo.toml index 2aad5886..514bef32 100644 --- a/examples/basic-shared-resource/Cargo.toml +++ b/examples/basic-shared-resource/Cargo.toml @@ -5,5 +5,5 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 0df7d8e2..36efee36 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -17,5 +17,5 @@ edition = "2021" [dependencies] aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-streaming-response/src/main.rs b/examples/basic-streaming-response/src/main.rs index 8533c8e3..fe112b80 100644 --- a/examples/basic-streaming-response/src/main.rs +++ b/examples/basic-streaming-response/src/main.rs @@ -7,7 +7,7 @@ use serde_json::Value; use std::{thread, time::Duration}; async fn func(_event: LambdaEvent) -> Result, Error> { - let messages = vec!["Hello", "world", "from", "Lambda!"]; + let messages = ["Hello", "world", "from", "Lambda!"]; let (mut tx, rx) = channel(); diff --git a/examples/extension-basic/Cargo.toml b/examples/extension-basic/Cargo.toml index 48e2ed51..d7cf1f13 100644 --- a/examples/extension-basic/Cargo.toml +++ b/examples/extension-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-combined/Cargo.toml b/examples/extension-combined/Cargo.toml index 2a745c7b..93aacca1 100644 --- a/examples/extension-combined/Cargo.toml +++ b/examples/extension-combined/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-custom-events/Cargo.toml b/examples/extension-custom-events/Cargo.toml index c2f813c3..dfef4c4b 100644 --- a/examples/extension-custom-events/Cargo.toml +++ b/examples/extension-custom-events/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-custom-service/Cargo.toml b/examples/extension-custom-service/Cargo.toml index b51eae8e..8d0e4575 100644 --- a/examples/extension-custom-service/Cargo.toml +++ b/examples/extension-custom-service/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-internal-flush/Cargo.toml b/examples/extension-internal-flush/Cargo.toml index 70e27ebf..1a0747dd 100644 --- a/examples/extension-internal-flush/Cargo.toml +++ b/examples/extension-internal-flush/Cargo.toml @@ -8,5 +8,5 @@ anyhow = "1" aws_lambda_events = { path = "../../lambda-events" } lambda-extension = { path = "../../lambda-extension" } lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.136" +serde = "1.0.219" tokio = { version = "1.46", features = ["macros", "sync"] } diff --git a/examples/extension-internal-flush/src/main.rs b/examples/extension-internal-flush/src/main.rs index 9a515ff8..d20213e1 100644 --- a/examples/extension-internal-flush/src/main.rs +++ b/examples/extension-internal-flush/src/main.rs @@ -2,8 +2,10 @@ use anyhow::anyhow; use aws_lambda_events::sqs::{SqsBatchResponse, SqsEventObj}; use lambda_extension::{service_fn, tracing, Error, Extension, NextEvent}; use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}; -use tokio::sync::Mutex; +use tokio::sync::{ + mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender}, + Mutex, +}; use std::sync::Arc; diff --git a/examples/extension-logs-basic/Cargo.toml b/examples/extension-logs-basic/Cargo.toml index dccc1ec0..230ebc7e 100644 --- a/examples/extension-logs-basic/Cargo.toml +++ b/examples/extension-logs-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } diff --git a/examples/extension-logs-custom-service/Cargo.toml b/examples/extension-logs-custom-service/Cargo.toml index 1b1eea0a..421fe9ff 100644 --- a/examples/extension-logs-custom-service/Cargo.toml +++ b/examples/extension-logs-custom-service/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/examples/extension-logs-kinesis-firehose/Cargo.toml b/examples/extension-logs-kinesis-firehose/Cargo.toml index c6675e5a..84f7ac96 100644 --- a/examples/extension-logs-kinesis-firehose/Cargo.toml +++ b/examples/extension-logs-kinesis-firehose/Cargo.toml @@ -5,6 +5,6 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -tokio = { version = "1.17.0", features = ["full"] } -aws-config = "0.13.0" -aws-sdk-firehose = "0.13.0" +tokio = { version = "1.46.1", features = ["full"] } +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-sdk-firehose = "1.82.0" diff --git a/examples/extension-logs-kinesis-firehose/src/main.rs b/examples/extension-logs-kinesis-firehose/src/main.rs index 7871ce52..c9d8a2e4 100644 --- a/examples/extension-logs-kinesis-firehose/src/main.rs +++ b/examples/extension-logs-kinesis-firehose/src/main.rs @@ -1,4 +1,4 @@ -use aws_sdk_firehose::{model::Record, types::Blob, Client}; +use aws_sdk_firehose::{primitives::Blob, types::Record, Client}; use lambda_extension::{tracing, Error, Extension, LambdaLog, LambdaLogRecord, Service, SharedService}; use std::{future::Future, pin::Pin, task::Poll}; @@ -31,7 +31,12 @@ impl Service> for FirehoseLogsProcessor { for log in logs { match log.record { LambdaLogRecord::Function(record) => { - records.push(Record::builder().data(Blob::new(record.as_bytes())).build()) + match Record::builder().data(Blob::new(record.as_bytes())).build() { + Ok(rec) => records.push(rec), + Err(e) => { + return Box::pin(async move { Err(e.into()) }); + } + } } _ => unreachable!(), } diff --git a/examples/extension-telemetry-basic/Cargo.toml b/examples/extension-telemetry-basic/Cargo.toml index 1b8b1ba4..a0fb6b87 100644 --- a/examples/extension-telemetry-basic/Cargo.toml +++ b/examples/extension-telemetry-basic/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda-extension = { path = "../../lambda-extension" } -serde = "1.0.136" tokio = { version = "1", features = ["macros", "rt"] } diff --git a/examples/http-axum-apigw-authorizer/Cargo.toml b/examples/http-axum-apigw-authorizer/Cargo.toml index c757aa94..44c50167 100644 --- a/examples/http-axum-apigw-authorizer/Cargo.toml +++ b/examples/http-axum-apigw-authorizer/Cargo.toml @@ -4,9 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.196" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-apigw-authorizer/src/main.rs b/examples/http-axum-apigw-authorizer/src/main.rs index 513a6cd8..8adb9024 100644 --- a/examples/http-axum-apigw-authorizer/src/main.rs +++ b/examples/http-axum-apigw-authorizer/src/main.rs @@ -1,5 +1,4 @@ use axum::{ - async_trait, extract::{FromRequest, Request}, http::StatusCode, response::Json, @@ -13,7 +12,6 @@ use std::{collections::HashMap, env::set_var}; struct AuthorizerField(String); struct AuthorizerFields(HashMap); -#[async_trait] impl FromRequest for AuthorizerField where S: Send + Sync, @@ -30,7 +28,6 @@ where } } -#[async_trait] impl FromRequest for AuthorizerFields where S: Send + Sync, diff --git a/examples/http-axum-diesel-ssl/Cargo.toml b/examples/http-axum-diesel-ssl/Cargo.toml index 69366957..d21df0b9 100755 --- a/examples/http-axum-diesel-ssl/Cargo.toml +++ b/examples/http-axum-diesel-ssl/Cargo.toml @@ -4,16 +4,14 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" -bb8 = "0.8.0" -diesel = "2.0.3" -diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +axum = "0.8" +diesel = "2.2.11" +diesel-async = { version = "0.6.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.159" -futures-util = "0.3.21" -rustls = "0.20.8" -rustls-native-certs = "0.6.2" -tokio = { version = "1.2.0", default-features = false, features = ["macros", "rt-multi-thread"] } -tokio-postgres = "0.7.7" -tokio-postgres-rustls = "0.9.0" \ No newline at end of file +serde = "1.0.219" +futures-util = "0.3.31" +rustls = "0.23.28" +rustls-native-certs = "0.8.1" +tokio = { version = "1.46.1", default-features = false, features = ["macros", "rt-multi-thread"] } +tokio-postgres = "0.7.13" +tokio-postgres-rustls = "0.13.0" diff --git a/examples/http-axum-diesel-ssl/src/main.rs b/examples/http-axum-diesel-ssl/src/main.rs index b340b44d..395c1843 100755 --- a/examples/http-axum-diesel-ssl/src/main.rs +++ b/examples/http-axum-diesel-ssl/src/main.rs @@ -1,19 +1,18 @@ -use diesel::{ConnectionError, ConnectionResult}; -use futures_util::future::BoxFuture; -use futures_util::FutureExt; -use std::time::Duration; - use axum::{ extract::{Path, State}, response::Json, routing::get, Router, }; -use bb8::Pool; -use diesel::prelude::*; -use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use diesel::{prelude::*, ConnectionError, ConnectionResult}; +use diesel_async::{ + pooled_connection::{bb8::Pool, AsyncDieselConnectionManager, ManagerConfig}, + AsyncPgConnection, RunQueryDsl, +}; +use futures_util::{future::BoxFuture, FutureExt}; use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; +use std::time::Duration; table! { posts (id) { @@ -40,7 +39,7 @@ struct NewPost { published: bool, } -type AsyncPool = Pool>; +type AsyncPool = Pool; type ServerError = (StatusCode, String); async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { @@ -104,7 +103,10 @@ async fn main() -> Result<(), Error> { // Format for DATABASE_URL=postgres://your_username:your_password@your_host:5432/your_db?sslmode=require let db_url = std::env::var("DATABASE_URL").expect("Env var `DATABASE_URL` not set"); - let mgr = AsyncDieselConnectionManager::::new_with_setup(db_url, establish_connection); + let mut config = ManagerConfig::default(); + config.custom_setup = Box::new(establish_connection); + + let mgr = AsyncDieselConnectionManager::::new_with_config(db_url, config); let pool = Pool::builder() .max_size(10) @@ -129,19 +131,15 @@ fn establish_connection(config: &str) -> BoxFuture BoxFuture rustls::RootCertStore { let mut roots = rustls::RootCertStore::empty(); let certs = rustls_native_certs::load_native_certs().expect("Certs not loadable!"); - let certs: Vec<_> = certs.into_iter().map(|cert| cert.0).collect(); - roots.add_parsable_certificates(&certs); + roots.add_parsable_certificates(certs); roots } diff --git a/examples/http-axum-diesel/Cargo.toml b/examples/http-axum-diesel/Cargo.toml index 39fc813e..bed36762 100644 --- a/examples/http-axum-diesel/Cargo.toml +++ b/examples/http-axum-diesel/Cargo.toml @@ -4,11 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" -bb8 = "0.8.0" -diesel = "2.0.3" -diesel-async = { version = "0.2.1", features = ["postgres", "bb8"] } +axum = "0.8" +diesel = "2.2.11" +diesel-async = { version = "0.6.1", features = ["postgres", "bb8"] } lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.159" +serde = "1.0.219" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-diesel/src/main.rs b/examples/http-axum-diesel/src/main.rs index b7247be4..348d7535 100644 --- a/examples/http-axum-diesel/src/main.rs +++ b/examples/http-axum-diesel/src/main.rs @@ -4,9 +4,11 @@ use axum::{ routing::get, Router, }; -use bb8::Pool; use diesel::prelude::*; -use diesel_async::{pooled_connection::AsyncDieselConnectionManager, AsyncPgConnection, RunQueryDsl}; +use diesel_async::{ + pooled_connection::{bb8::Pool, AsyncDieselConnectionManager}, + AsyncPgConnection, RunQueryDsl, +}; use lambda_http::{http::StatusCode, run, tracing, Error}; use serde::{Deserialize, Serialize}; @@ -35,7 +37,7 @@ struct NewPost { published: bool, } -type AsyncPool = Pool>; +type AsyncPool = Pool; type ServerError = (StatusCode, String); async fn create_post(State(pool): State, Json(post): Json) -> Result, ServerError> { diff --git a/examples/http-axum-middleware/Cargo.toml b/examples/http-axum-middleware/Cargo.toml index 228fc0ae..f3966941 100644 --- a/examples/http-axum-middleware/Cargo.toml +++ b/examples/http-axum-middleware/Cargo.toml @@ -4,10 +4,9 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http", default-features = false, features = [ "apigw_rest", "tracing" ] } -lambda_runtime = { path = "../../lambda-runtime" } serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum-middleware/src/main.rs b/examples/http-axum-middleware/src/main.rs index b1e92811..e335c270 100644 --- a/examples/http-axum-middleware/src/main.rs +++ b/examples/http-axum-middleware/src/main.rs @@ -10,8 +10,7 @@ //! ``` use axum::{response::Json, routing::post, Router}; -use lambda_http::request::RequestContext::ApiGatewayV1; -use lambda_http::{run, tracing, Error}; +use lambda_http::{request::RequestContext::ApiGatewayV1, run, tracing, Error}; use serde_json::{json, Value}; // Sample middleware that logs the request id diff --git a/examples/http-axum/Cargo.toml b/examples/http-axum/Cargo.toml index 7ab3c0ec..7664e7a7 100644 --- a/examples/http-axum/Cargo.toml +++ b/examples/http-axum/Cargo.toml @@ -4,9 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -axum = "0.7" +axum = "0.8" lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } -serde = "1.0.196" +serde = "1.0.219" serde_json = "1.0" tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-axum/src/main.rs b/examples/http-axum/src/main.rs index dcd5d154..3b00fc57 100644 --- a/examples/http-axum/src/main.rs +++ b/examples/http-axum/src/main.rs @@ -6,10 +6,9 @@ //! 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 axum::extract::Query; -use axum::http::StatusCode; use axum::{ - extract::Path, + extract::{Path, Query}, + http::StatusCode, response::Json, routing::{get, post}, Router, diff --git a/examples/http-basic-lambda/Cargo.toml b/examples/http-basic-lambda/Cargo.toml index c7a51507..2f252389 100644 --- a/examples/http-basic-lambda/Cargo.toml +++ b/examples/http-basic-lambda/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-cors/Cargo.toml b/examples/http-cors/Cargo.toml index b8e51031..b9c9efa5 100644 --- a/examples/http-cors/Cargo.toml +++ b/examples/http-cors/Cargo.toml @@ -5,6 +5,5 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.5", features = ["cors"] } +tower-http = { version = "0.6", features = ["cors"] } diff --git a/examples/http-cors/src/main.rs b/examples/http-cors/src/main.rs index ffac0a9e..4c2c54b4 100644 --- a/examples/http-cors/src/main.rs +++ b/examples/http-cors/src/main.rs @@ -28,7 +28,7 @@ async fn func(event: Request) -> Result, Error> { .query_string_parameters_ref() .and_then(|params| params.first("first_name")) { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + Some(first_name) => format!("Hello, {first_name}!").into_response().await, None => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-dynamodb/Cargo.toml b/examples/http-dynamodb/Cargo.toml index f2b8db98..d347c346 100644 --- a/examples/http-dynamodb/Cargo.toml +++ b/examples/http-dynamodb/Cargo.toml @@ -4,11 +4,10 @@ version = "0.1.0" edition = "2021" [dependencies] -simple-error = "0.3.0" -serde_json = "1.0.107" -serde = { version = "1.0.189", features = ["derive"] } -serde_dynamo = {version = "^4.2.7", features = ["aws-sdk-dynamodb+0_33"]} +serde_json = "1.0.140" +serde = { version = "1.0.219", features = ["derive"] } +serde_dynamo = { version = "4.2.14", features = ["aws-sdk-dynamodb+1"] } lambda_http = { path = "../../lambda-http" } -aws-sdk-dynamodb = "0.33.0" -aws-config = "0.56.1" -tokio = { version = "1.33.0", features = ["macros"] } +aws-sdk-dynamodb = "1.82.0" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +tokio = { version = "1.46.1", features = ["macros"] } diff --git a/examples/http-dynamodb/src/main.rs b/examples/http-dynamodb/src/main.rs index 2bf3fb9d..0d37693f 100644 --- a/examples/http-dynamodb/src/main.rs +++ b/examples/http-dynamodb/src/main.rs @@ -1,15 +1,15 @@ use aws_sdk_dynamodb::Client; use lambda_http::{run, service_fn, tracing, Body, Error, Request, Response}; use serde::{Deserialize, Serialize}; -use serde_dynamo::to_attribute_value; +use serde_dynamo::to_item; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Item { - pub p_type: String, + pub account_type: String, pub age: String, pub username: String, - pub first: String, - pub last: String, + pub first_name: String, + pub last_name: String, } /// This is the main body for the function. @@ -71,20 +71,9 @@ async fn main() -> Result<(), Error> { // Add an item to a table. // snippet-start:[dynamodb.rust.add-item] pub async fn add_item(client: &Client, item: Item, table: &str) -> Result<(), Error> { - let user_av = to_attribute_value(item.username)?; - let type_av = to_attribute_value(item.p_type)?; - let age_av = to_attribute_value(item.age)?; - let first_av = to_attribute_value(item.first)?; - let last_av = to_attribute_value(item.last)?; + let item = to_item(item)?; - let request = client - .put_item() - .table_name(table) - .item("username", user_av) - .item("account_type", type_av) - .item("age", age_av) - .item("first_name", first_av) - .item("last_name", last_av); + let request = client.put_item().table_name(table).set_item(Some(item)); tracing::info!("adding item to DynamoDB"); diff --git a/examples/http-query-parameters/Cargo.toml b/examples/http-query-parameters/Cargo.toml index 2cadda95..18f8e6cf 100644 --- a/examples/http-query-parameters/Cargo.toml +++ b/examples/http-query-parameters/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-query-parameters/src/main.rs b/examples/http-query-parameters/src/main.rs index ef9cf658..c974e633 100644 --- a/examples/http-query-parameters/src/main.rs +++ b/examples/http-query-parameters/src/main.rs @@ -11,7 +11,7 @@ async fn function_handler(event: Request) -> Result { .query_string_parameters_ref() .and_then(|params| params.first("first_name")) { - Some(first_name) => format!("Hello, {}!", first_name).into_response().await, + Some(first_name) => format!("Hello, {first_name}!").into_response().await, None => Response::builder() .status(400) .body("Empty first name".into()) diff --git a/examples/http-raw-path/Cargo.toml b/examples/http-raw-path/Cargo.toml index f6c56526..d1c5ccb8 100644 --- a/examples/http-raw-path/Cargo.toml +++ b/examples/http-raw-path/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-shared-resource/Cargo.toml b/examples/http-shared-resource/Cargo.toml index 923ceecc..8f5a0e94 100644 --- a/examples/http-shared-resource/Cargo.toml +++ b/examples/http-shared-resource/Cargo.toml @@ -5,5 +5,4 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = { path = "../../lambda-runtime" } tokio = { version = "1", features = ["macros"] } diff --git a/examples/http-tower-trace/Cargo.toml b/examples/http-tower-trace/Cargo.toml index 36389f0e..cf1f223b 100644 --- a/examples/http-tower-trace/Cargo.toml +++ b/examples/http-tower-trace/Cargo.toml @@ -5,6 +5,5 @@ edition = "2021" [dependencies] lambda_http = { path = "../../lambda-http" } -lambda_runtime = "0.5.1" tokio = { version = "1", features = ["macros"] } -tower-http = { version = "0.5", features = ["trace"] } +tower-http = { version = "0.6", features = ["trace"] } diff --git a/examples/http-tower-trace/src/main.rs b/examples/http-tower-trace/src/main.rs index df2b9007..7dd0c1d6 100644 --- a/examples/http-tower-trace/src/main.rs +++ b/examples/http-tower-trace/src/main.rs @@ -1,6 +1,9 @@ -use lambda_http::tracing::{self, Level}; -use lambda_http::{run, tower::ServiceBuilder, Error}; -use lambda_http::{Request, Response}; +use lambda_http::{ + run, + tower::ServiceBuilder, + tracing::{self, Level}, + Error, Request, Response, +}; use tower_http::trace::{DefaultOnRequest, DefaultOnResponse, TraceLayer}; async fn handler(_req: Request) -> Result, Error> { diff --git a/examples/lambda-rds-iam-auth/Cargo.toml b/examples/lambda-rds-iam-auth/Cargo.toml index a1e212ae..68b0fe92 100644 --- a/examples/lambda-rds-iam-auth/Cargo.toml +++ b/examples/lambda-rds-iam-auth/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime" } -serde_json = "1.0.120" -aws-config = "1.0.1" -aws-credential-types = "1.0.1" -aws-sigv4 = "1.0.1" -url = "2.5.0" -tokio = { version = "1.25.0", features = ["full"] } -sqlx = { version = "0.7.4", features = ["tls-rustls", "postgres", "runtime-tokio"] } +serde_json = "1.0.140" +aws-config = { version = "1.8.1", features = ["behavior-version-latest"] } +aws-credential-types = "1.2.3" +aws-sigv4 = "1.3.3" +url = "2.5.4" +tokio = { version = "1.46.1", features = ["full"] } +sqlx = { version = "0.8.6", features = ["tls-rustls", "postgres", "runtime-tokio"] } diff --git a/examples/lambda-rds-iam-auth/src/main.rs b/examples/lambda-rds-iam-auth/src/main.rs index 32cf3580..fbf8394f 100644 --- a/examples/lambda-rds-iam-auth/src/main.rs +++ b/examples/lambda-rds-iam-auth/src/main.rs @@ -7,17 +7,15 @@ use aws_sigv4::{ use lambda_runtime::{run, service_fn, Error, LambdaEvent}; use serde_json::{json, Value}; use sqlx::postgres::PgConnectOptions; -use std::env; -use std::time::{Duration, SystemTime}; +use std::{ + env, + time::{Duration, SystemTime}, +}; const RDS_CERTS: &[u8] = include_bytes!("global-bundle.pem"); -async fn generate_rds_iam_token( - db_hostname: &str, - port: u16, - db_username: &str, -) -> Result { - let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await; +async fn generate_rds_iam_token(db_hostname: &str, port: u16, db_username: &str) -> Result { + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; let credentials = config .credentials_provider() @@ -40,23 +38,16 @@ async fn generate_rds_iam_token( .settings(signing_settings) .build()?; - let url = format!( - "https://{db_hostname}:{port}/?Action=connect&DBUser={db_user}", - db_hostname = db_hostname, - port = port, - db_user = db_username - ); + let url = format!("https://{db_hostname}:{port}/?Action=connect&DBUser={db_username}"); let signable_request = - SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])) - .expect("signable request"); + SignableRequest::new("GET", &url, std::iter::empty(), SignableBody::Bytes(&[])).expect("signable request"); - let (signing_instructions, _signature) = - sign(signable_request, &signing_params.into())?.into_parts(); + let (signing_instructions, _signature) = sign(signable_request, &signing_params.into())?.into_parts(); let mut url = url::Url::parse(&url).unwrap(); for (name, value) in signing_instructions.params() { - url.query_pairs_mut().append_pair(name, &value); + url.query_pairs_mut().append_pair(name, value); } let response = url.to_string().split_off("https://".len()); @@ -89,9 +80,7 @@ async fn handler(_event: LambdaEvent) -> Result { .ssl_root_cert_from_pem(RDS_CERTS.to_vec()) .ssl_mode(sqlx::postgres::PgSslMode::Require); - let pool = sqlx::postgres::PgPoolOptions::new() - .connect_with(opts) - .await?; + let pool = sqlx::postgres::PgPoolOptions::new().connect_with(opts).await?; let result: i32 = sqlx::query_scalar("SELECT $1 + $2") .bind(3) @@ -99,7 +88,7 @@ async fn handler(_event: LambdaEvent) -> Result { .fetch_one(&pool) .await?; - println!("Result: {:?}", result); + println!("Result: {result:?}"); Ok(json!({ "statusCode": 200, diff --git a/examples/opentelemetry-tracing/Cargo.toml b/examples/opentelemetry-tracing/Cargo.toml index 98108d39..c9b2a9cb 100644 --- a/examples/opentelemetry-tracing/Cargo.toml +++ b/examples/opentelemetry-tracing/Cargo.toml @@ -5,14 +5,12 @@ edition = "2021" [dependencies] lambda_runtime = { path = "../../lambda-runtime", features = ["opentelemetry"] } -opentelemetry-semantic-conventions = "0.14" -opentelemetry = "0.22" -opentelemetry_sdk = { version = "0.22", features = ["rt-tokio"] } -opentelemetry-stdout = { version = "0.3", features = ["trace"] } -pin-project = "1" +opentelemetry = "0.30" +opentelemetry_sdk = { version = "0.30", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.30", features = ["trace"] } serde_json = "1.0" tokio = "1" tower = "0.5" tracing = "0.1" -tracing-opentelemetry = "0.23" +tracing-opentelemetry = "0.31" tracing-subscriber = "0.3" diff --git a/examples/opentelemetry-tracing/src/main.rs b/examples/opentelemetry-tracing/src/main.rs index 062f5a11..a75aa016 100644 --- a/examples/opentelemetry-tracing/src/main.rs +++ b/examples/opentelemetry-tracing/src/main.rs @@ -4,7 +4,7 @@ use lambda_runtime::{ LambdaEvent, Runtime, }; use opentelemetry::trace::TracerProvider; -use opentelemetry_sdk::{runtime, trace}; +use opentelemetry_sdk::trace; use tower::{service_fn, BoxError}; use tracing_subscriber::prelude::*; @@ -18,8 +18,8 @@ async fn echo(event: LambdaEvent) -> Result Result<(), BoxError> { // Set up OpenTelemetry tracer provider that writes spans to stdout for debugging purposes let exporter = opentelemetry_stdout::SpanExporter::default(); - let tracer_provider = trace::TracerProvider::builder() - .with_batch_exporter(exporter, runtime::Tokio) + let tracer_provider = trace::SdkTracerProvider::builder() + .with_batch_exporter(exporter) .build(); // Set up link between OpenTelemetry and tracing crate @@ -34,7 +34,9 @@ async fn main() -> Result<(), BoxError> { // Create a tracing span for each Lambda invocation OtelLayer::new(|| { // Make sure that the trace is exported before the Lambda runtime is frozen - tracer_provider.force_flush(); + if let Err(err) = tracer_provider.force_flush() { + eprintln!("Error flushing traces: {err:#?}"); + } }) // Set the "faas.trigger" attribute of the span to "pubsub" .with_trigger(OpenTelemetryFaasTrigger::PubSub), diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 4754ffbe..d178520a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -20,7 +20,6 @@ tracing = ["lambda_runtime_api_client/tracing"] [dependencies] async-stream = "0.3" -bytes = { workspace = true } chrono = { workspace = true, features = ["serde"] } http = { workspace = true } http-body-util = { workspace = true } diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 38b65a56..4498e275 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -30,10 +30,8 @@ eyre = ["lambda_runtime/eyre"] # enables From for Diagnostic for eyre error t miette = ["lambda_runtime/miette"] # enables From for Diagnostic for miette error types, see README.md for more info [dependencies] -base64 = { workspace = true } bytes = { workspace = true } encoding_rs = "0.8" -futures = { workspace = true } futures-util = { workspace = true } http = { workspace = true } http-body = { workspace = true } @@ -56,8 +54,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] -axum-core = "0.4.3" -axum-extra = { version = "0.9.2", features = ["query"] } +axum-core = "0.5.0" +axum-extra = { version = "0.10.0", features = ["query"] } lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index f0bdb292..0a9836c4 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -20,7 +20,6 @@ serde = { version = "1.0.204", features = ["derive"] } [dev-dependencies] reqwest = { version = "0.12.5", features = ["blocking"] } -openssl = { version = "0.10", features = ["vendored"] } [[bin]] name = "helloworld" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index 640560e0..cc2289af 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -33,8 +33,6 @@ hyper-util = { workspace = true, features = [ "tokio", ] } tower = { workspace = true, features = ["util"] } -tower-service = { workspace = true } -tokio = { version = "1.0", features = ["io-util"] } tracing = { version = "0.1", features = ["log"], optional = true } tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "json", "env-filter"], optional = true } diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index c4787d56..6e0dde73 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -34,16 +34,9 @@ bytes = { workspace = true } eyre = { version = "0.6.12", optional = true } futures = { workspace = true } http = { workspace = true } -http-body = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } hyper = { workspace = true, features = ["http1", "client"] } -hyper-util = { workspace = true, features = [ - "client", - "client-legacy", - "http1", - "tokio", -] } lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } @@ -60,7 +53,6 @@ tokio = { version = "1.46", features = [ ] } tokio-stream = "0.1.2" tower = { workspace = true, features = ["util"] } -tower-layer = { workspace = true } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] From fd6f5288f3a508a0302fe8a1ed3008d5df63d443 Mon Sep 17 00:00:00 2001 From: George Lewis <33588728+George-lewis@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:41:54 -0400 Subject: [PATCH 192/211] Capture `String` Panic Messages (#962) --- lambda-runtime/src/layers/panic.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lambda-runtime/src/layers/panic.rs b/lambda-runtime/src/layers/panic.rs index 4b92e3c8..257a8f39 100644 --- a/lambda-runtime/src/layers/panic.rs +++ b/lambda-runtime/src/layers/panic.rs @@ -99,6 +99,8 @@ impl CatchPanicFuture<'_, F> { fn build_panic_diagnostic(err: &Box) -> Diagnostic { let error_message = if let Some(msg) = err.downcast_ref::<&str>() { format!("Lambda panicked: {msg}") + } else if let Some(msg) = err.downcast_ref::() { + format!("Lambda panicked: {msg}") } else { "Lambda panicked".to_string() }; From 35860b04ac0fa6ec4e152299e20d9a94c6d3db7a Mon Sep 17 00:00:00 2001 From: Spencer Stolworthy Date: Thu, 10 Jul 2025 08:57:30 -0700 Subject: [PATCH 193/211] fix(lambda-events): impl Default for all Kinesis Event structs (#1011) --- lambda-events/src/encodings/time.rs | 2 +- lambda-events/src/event/kinesis/event.rs | 18 ++++++++++++------ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index df22ef24..d0d9526e 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -29,7 +29,7 @@ impl DerefMut for MillisecondTimestamp { } /// Timestamp with second precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondTimestamp( #[serde(deserialize_with = "deserialize_seconds")] #[serde(serialize_with = "serialize_seconds")] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 97f4b708..a67da850 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -13,7 +13,7 @@ pub struct KinesisEvent { /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { #[serde(rename = "KinesisEvent")] @@ -34,7 +34,7 @@ pub struct KinesisTimeWindowEventResponse { // pub batch_item_failures: Vec, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventRecord { /// nolint: stylecheck @@ -59,7 +59,7 @@ pub struct KinesisEventRecord { pub kinesis: KinesisRecord, } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisRecord { pub approximate_arrival_timestamp: SecondTimestamp, @@ -110,10 +110,16 @@ mod test { assert_eq!(parsed, reparsed); } + /// `cargo lambda init` autogenerates code that relies on `Default` being implemented for event structs. + /// + /// This test validates that `Default` is implemented for each KinesisEvent struct. #[test] #[cfg(feature = "kinesis")] - fn default_kinesis_event() { - let event = KinesisEvent::default(); - assert_eq!(event.records, vec![]); + fn test_ensure_default_implemented_for_structs() { + let _kinesis_event = KinesisEvent::default(); + let _kinesis_time_window_event = KinesisTimeWindowEvent::default(); + let _kinesis_event_record = KinesisEventRecord::default(); + let _kinesis_record = KinesisRecord::default(); + let _kinesis_encryption_type = KinesisEncryptionType::default(); } } From fd0354eac86c84cb0e0792d7a8cd9e2f1d60cd94 Mon Sep 17 00:00:00 2001 From: Bryson M <43580701+Bryson14@users.noreply.github.com> Date: Thu, 10 Jul 2025 16:50:52 -0700 Subject: [PATCH 194/211] Add alternative to pip3 install of cargo lambda (#981) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When running `pip3 install .....` it shows this error about system vs user installation: ``` rror: externally-managed-environment × This environment is externally managed ╰─> To install Python packages system-wide, try apt install python3-xyz, where xyz is the package you are trying to install. If you wish to install a non-Debian-packaged Python package, create a virtual environment using python3 -m venv path/to/venv. Then use path/to/venv/bin/python and path/to/venv/bin/pip. Make sure you have python3-full installed. If you wish to install a non-Debian packaged Python application, it may be easiest to use pipx install xyz, which will manage a virtual environment for you. Make sure you have pipx installed. See /usr/share/doc/python3.12/README.venv for more information. ``` Another way to manage executable pip packages is with uv which has been very popular and helpful in the python community. ``` --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 89f40f92..5426c0c9 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,11 @@ Or PiP on any system with Python 3 installed: ```bash pip3 install cargo-lambda ``` +Alternative, install the pip package as an executable using [uv](https://docs.astral.sh/uv/) + +```bash +uv tool install cargo-lambda +``` See other installation options in [the Cargo Lambda documentation](https://www.cargo-lambda.info/guide/installation.html). From ee2e0d295a1c95e27cf474086839c7c2c76fb00d Mon Sep 17 00:00:00 2001 From: Ikuma Yamashita Date: Sat, 12 Jul 2025 06:45:25 +0900 Subject: [PATCH 195/211] Add support for Direct Lambda Resolver event format (#1015) * feat(event): add AppSyncResolverEvent structure and example payload * fix: remove `groups` field from AppSyncCognitoIdentity to avoid semver break * fix: apply serde annotations and generic types to align with existing conventions * refactor: rename AppSyncResolverEvent to AppSyncDirectResolverEvent * fix: update doc comment to reflect AppSyncDirectResolverEvent rename --- lambda-events/src/event/appsync/mod.rs | 116 ++++++++++++++++++ .../example-appsync-direct-resolver.json | 64 ++++++++++ 2 files changed, 180 insertions(+) create mode 100644 lambda-events/src/fixtures/example-appsync-direct-resolver.json diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 32bf9f79..4aa62e5b 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -117,6 +117,112 @@ where pub ttl_override: Option, } +/// `AppSyncDirectResolverEvent` represents the default payload structure sent by AWS AppSync +/// when using **Direct Lambda Resolvers** (i.e., when both request and response mapping +/// templates are disabled). +/// +/// This structure includes the full AppSync **Context object**, as described in the +/// [AppSync Direct Lambda resolver reference](https://docs.aws.amazon.com/appsync/latest/devguide/direct-lambda-reference.html). +/// +/// It is recommended when working without VTL templates and relying on the standard +/// AppSync-to-Lambda event format. +/// +/// See also: +/// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncDirectResolverEvent +where + TArguments: Serialize + DeserializeOwned, + TSource: Serialize + DeserializeOwned, + TStash: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub arguments: Option, + pub identity: Option, + #[serde(bound = "")] + pub source: Option, + pub request: AppSyncRequest, + pub info: AppSyncInfo, + #[serde(default)] + pub prev: Option, + #[serde(bound = "")] + pub stash: TStash, +} + +/// `AppSyncRequest` contains request-related metadata for a resolver invocation, +/// including client-sent headers and optional custom domain name. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub headers: HashMap>, + #[serde(default)] + pub domain_name: Option, +} + +/// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncInfo +where + T: Serialize + DeserializeOwned, +{ + #[serde(default)] + pub selection_set_list: Vec, + #[serde(rename = "selectionSetGraphQL")] + pub selection_set_graphql: String, + pub parent_type_name: String, + pub field_name: String, + #[serde(bound = "")] + pub variables: T, +} + +/// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncPrevResult +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub result: T, +} + +/// `AppSyncIdentity` represents the identity of the caller as determined by the +/// configured AppSync authorization mechanism (IAM, Cognito, OIDC, or Lambda). +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(untagged, rename_all = "camelCase")] +pub enum AppSyncIdentity { + IAM(AppSyncIamIdentity), + Cognito(AppSyncCognitoIdentity), + OIDC(AppSyncIdentityOIDC), + Lambda(AppSyncIdentityLambda), +} + +/// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +pub struct AppSyncIdentityOIDC +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub claims: T, + pub issuer: String, + pub sub: String, +} + +/// `AppSyncIdentityLambda` represents identity information when using AWS Lambda +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncIdentityLambda +where + T: Serialize + DeserializeOwned, +{ + #[serde(bound = "")] + pub resolver_context: T, +} + #[cfg(test)] mod test { use super::*; @@ -160,4 +266,14 @@ mod test { let reparsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_direct_resolver() { + let data = include_bytes!("../../fixtures/example-appsync-direct-resolver.json"); + let parsed: AppSyncDirectResolverEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncDirectResolverEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } } diff --git a/lambda-events/src/fixtures/example-appsync-direct-resolver.json b/lambda-events/src/fixtures/example-appsync-direct-resolver.json new file mode 100644 index 00000000..9a804876 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-direct-resolver.json @@ -0,0 +1,64 @@ +{ + "arguments": { + "input": "foo" + }, + "identity": { + "sourceIp": [ + "x.x.x.x" + ], + "userArn": "arn:aws:iam::123456789012:user/appsync", + "accountId": "666666666666", + "user": "AIDAAAAAAAAAAAAAAAAAA" + }, + "info": { + "fieldName": "greet", + "parentTypeName": "Query", + "selectionSetGraphQL": "", + "selectionSetList": [], + "variables": { + "inputVar": "foo" + } + }, + "prev": null, + "request": { + "domainName": null, + "headers": { + "accept": "application/json, text/plain, */*", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.9,ja;q=0.8,en-GB;q=0.7", + "cloudfront-forwarded-proto": "https", + "cloudfront-is-desktop-viewer": "true", + "cloudfront-is-mobile-viewer": "false", + "cloudfront-is-smarttv-viewer": "false", + "cloudfront-is-tablet-viewer": "false", + "cloudfront-viewer-asn": "17676", + "cloudfront-viewer-country": "JP", + "content-length": "40", + "content-type": "application/json", + "host": "2ojpkjk2ejb57l7stgad5o4qiq.appsync-api.ap-northeast-1.amazonaws.com", + "origin": "https://ap-northeast-1.console.aws.amazon.com", + "priority": "u=1, i", + "referer": "https://ap-northeast-1.console.aws.amazon.com/", + "sec-ch-ua": "\"Not)A;Brand\";v=\"8\", \"Chromium\";v=\"138\", \"Microsoft Edge\";v=\"138\"", + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": "\"Windows\"", + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "cross-site", + "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 Edg/138.0.0.0", + "via": "2.0 ee337d4db5c7ebfdc8ec0798a1ede776.cloudfront.net (CloudFront)", + "x-amz-cf-id": "O3ZflUCq6_TzxjouyYB3zg7-kl7Ze-gXbniM2jJ3hAOfDFpPMGRu3Q==", + "x-amz-user-agent": "AWS-Console-AppSync/", + "x-amzn-appsync-is-vpce-request": "false", + "x-amzn-remote-ip": "x.x.x.x", + "x-amzn-requestid": "7ada8740-bbf4-49e8-bf45-f10b3d67159b", + "x-amzn-trace-id": "Root=1-68713e21-7a03739120ad60703e794b22", + "x-api-key": "***", + "x-forwarded-for": "***", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + } + }, + "source": null, + "stash": {} +} \ No newline at end of file From 0cdd565fa0c4886243bdf54ba7b1e3c5bf692721 Mon Sep 17 00:00:00 2001 From: Craig Disselkoen Date: Thu, 17 Jul 2025 09:37:37 -0700 Subject: [PATCH 196/211] add catchall feature to resolve #1017 (#1018) * add catchall feature to resolve #1017 Signed-off-by: Craig Disselkoen * switch from HashMap to serde_json::Map Signed-off-by: Craig Disselkoen * add cfg_attr for docsrs Signed-off-by: Craig Disselkoen * bump lambda_events version to 0.17 Signed-off-by: Craig Disselkoen * tweak comment wording Signed-off-by: Craig Disselkoen --------- Signed-off-by: Craig Disselkoen --- lambda-events/Cargo.toml | 6 +- lambda-events/src/event/activemq/mod.rs | 23 ++ lambda-events/src/event/alb/mod.rs | 30 ++ lambda-events/src/event/apigw/mod.rs | 235 +++++++++++++ lambda-events/src/event/appsync/mod.rs | 84 +++++ lambda-events/src/event/autoscaling/mod.rs | 7 + .../src/event/bedrock_agent_runtime/mod.rs | 44 +++ lambda-events/src/event/chime_bot/mod.rs | 30 ++ lambda-events/src/event/clientvpn/mod.rs | 16 + lambda-events/src/event/cloudformation/mod.rs | 28 ++ .../src/event/cloudformation/provider.rs | 17 + .../src/event/cloudwatch_alarms/mod.rs | 91 ++++++ .../src/event/cloudwatch_events/cloudtrail.rs | 49 +++ .../src/event/cloudwatch_events/codedeploy.rs | 23 ++ .../event/cloudwatch_events/codepipeline.rs | 30 ++ .../src/event/cloudwatch_events/ec2.rs | 9 + .../src/event/cloudwatch_events/emr.rs | 30 ++ .../src/event/cloudwatch_events/gamelift.rs | 86 +++++ .../src/event/cloudwatch_events/glue.rs | 58 ++++ .../src/event/cloudwatch_events/health.rs | 23 ++ .../src/event/cloudwatch_events/kms.rs | 9 + .../src/event/cloudwatch_events/macie.rs | 129 ++++++++ .../src/event/cloudwatch_events/mod.rs | 7 + .../src/event/cloudwatch_events/opsworks.rs | 30 ++ .../src/event/cloudwatch_events/signin.rs | 28 ++ .../src/event/cloudwatch_events/sms.rs | 9 + .../src/event/cloudwatch_events/ssm.rs | 100 ++++++ .../src/event/cloudwatch_events/tag.rs | 9 + .../event/cloudwatch_events/trustedadvisor.rs | 9 + .../src/event/cloudwatch_logs/mod.rs | 25 ++ lambda-events/src/event/code_commit/mod.rs | 33 ++ lambda-events/src/event/codebuild/mod.rs | 63 ++++ lambda-events/src/event/codedeploy/mod.rs | 23 ++ .../src/event/codepipeline_cloudwatch/mod.rs | 23 ++ .../src/event/codepipeline_job/mod.rs | 79 +++++ lambda-events/src/event/cognito/mod.rs | 309 +++++++++++++++++- lambda-events/src/event/config/mod.rs | 9 + lambda-events/src/event/connect/mod.rs | 37 +++ .../event/documentdb/events/commom_types.rs | 42 +++ .../event/documentdb/events/delete_event.rs | 9 + .../documentdb/events/drop_database_event.rs | 9 + .../src/event/documentdb/events/drop_event.rs | 9 + .../event/documentdb/events/insert_event.rs | 9 + .../documentdb/events/invalidate_event.rs | 9 + .../event/documentdb/events/rename_event.rs | 9 + .../event/documentdb/events/replace_event.rs | 9 + .../event/documentdb/events/update_event.rs | 23 ++ lambda-events/src/event/documentdb/mod.rs | 16 + lambda-events/src/event/dynamodb/mod.rs | 44 +++ lambda-events/src/event/ecr_scan/mod.rs | 23 ++ lambda-events/src/event/eventbridge/mod.rs | 7 + lambda-events/src/event/firehose/mod.rs | 44 +++ lambda-events/src/event/iam/mod.rs | 18 + lambda-events/src/event/iot/mod.rs | 51 +++ lambda-events/src/event/iot_1_click/mod.rs | 37 +++ lambda-events/src/event/iot_button/mod.rs | 9 + lambda-events/src/event/iot_deprecated/mod.rs | 16 + lambda-events/src/event/kafka/mod.rs | 16 + lambda-events/src/event/kinesis/analytics.rs | 30 ++ lambda-events/src/event/kinesis/event.rs | 37 +++ .../src/event/lambda_function_urls/mod.rs | 44 +++ lambda-events/src/event/lex/mod.rs | 65 ++++ lambda-events/src/event/rabbitmq/mod.rs | 21 ++ lambda-events/src/event/s3/batch_job.rs | 37 +++ lambda-events/src/event/s3/event.rs | 51 +++ lambda-events/src/event/s3/object_lambda.rs | 70 ++++ lambda-events/src/event/secretsmanager/mod.rs | 9 + lambda-events/src/event/ses/mod.rs | 72 ++++ lambda-events/src/event/sns/mod.rs | 100 ++++++ lambda-events/src/event/sqs/mod.rs | 79 +++++ lambda-events/src/event/streams/mod.rs | 44 +++ .../example-apigw-request-catch-all.json | 137 ++++++++ lambda-http/Cargo.toml | 3 +- lambda-http/src/response.rs | 12 + lambda-integration-tests/Cargo.toml | 3 + lambda-integration-tests/src/authorizer.rs | 6 + lambda-integration-tests/src/helloworld.rs | 2 + 77 files changed, 3074 insertions(+), 7 deletions(-) create mode 100644 lambda-events/src/fixtures/example-apigw-request-catch-all.json diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 481f77ba..cd2d86f2 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.16.1" +version = "0.17.0" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ @@ -125,5 +125,7 @@ streams = [] documentdb = [] eventbridge = ["chrono", "serde_with"] +catch-all-fields = [] + [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 89cfda1c..d9283bea 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -11,6 +13,13 @@ pub struct ActiveMqEvent { #[serde(default)] pub event_source_arn: Option, pub messages: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -41,6 +50,13 @@ pub struct ActiveMqMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub properties: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -48,6 +64,13 @@ pub struct ActiveMqMessage { pub struct ActiveMqDestination { #[serde(default)] pub physical_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 3b4ce9d5..55e427e2 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -7,6 +7,8 @@ use crate::{ use http::{HeaderMap, Method}; use query_map::QueryMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -30,6 +32,13 @@ pub struct AlbTargetGroupRequest { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub body: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda @@ -37,6 +46,13 @@ pub struct AlbTargetGroupRequest { #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequestContext { pub elb: ElbContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ElbContext` contains the information to identify the ARN invoking the lambda @@ -46,6 +62,13 @@ pub struct ElbContext { /// nolint: stylecheck #[serde(default)] pub target_group_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request @@ -65,6 +88,13 @@ pub struct AlbTargetGroupResponse { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index e0aa1e8c..c7a6175b 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -47,6 +47,13 @@ pub struct ApiGatewayProxyRequest { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request @@ -64,6 +71,13 @@ pub struct ApiGatewayProxyResponse { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the @@ -110,6 +124,13 @@ pub struct ApiGatewayProxyRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway @@ -160,6 +181,13 @@ pub struct ApiGatewayV2httpRequest { pub body: Option, #[serde(default)] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. @@ -192,6 +220,13 @@ pub struct ApiGatewayV2httpRequestContext { pub http: ApiGatewayV2httpRequestContextHttpDescription, #[serde(skip_serializing_if = "Option::is_none")] pub authentication: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. @@ -209,6 +244,13 @@ pub struct ApiGatewayRequestAuthorizer { pub fields: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub iam: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. @@ -220,6 +262,13 @@ pub struct ApiGatewayRequestAuthorizerJwtDescription { pub claims: HashMap, #[serde(skip_serializing_if = "Option::is_none")] pub scopes: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. @@ -240,6 +289,13 @@ pub struct ApiGatewayRequestAuthorizerIamDescription { pub user_arn: Option, #[serde(default)] pub user_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. @@ -251,6 +307,13 @@ pub struct ApiGatewayRequestAuthorizerCognitoIdentity { pub identity_id: Option, #[serde(default)] pub identity_pool_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. @@ -267,6 +330,13 @@ pub struct ApiGatewayV2httpRequestContextHttpDescription { pub source_ip: Option, #[serde(default)] pub user_agent: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request @@ -285,6 +355,13 @@ pub struct ApiGatewayV2httpResponse { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, pub cookies: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayRequestIdentity` contains identity information for the request caller. @@ -318,6 +395,13 @@ pub struct ApiGatewayRequestIdentity { pub user_agent: Option, #[serde(default)] pub user: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy @@ -357,6 +441,13 @@ pub struct ApiGatewayWebsocketProxyRequest { pub body: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify @@ -421,6 +512,13 @@ pub struct ApiGatewayWebsocketProxyRequestContext { pub disconnect_status_code: Option, #[serde(default)] pub disconnect_reason: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. @@ -435,6 +533,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { pub source_ip: Option, #[serde(default)] pub client_cert: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. @@ -452,6 +557,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { #[serde(rename = "subjectDN")] pub subject_dn: Option, pub validity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. @@ -462,6 +574,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit pub not_after: Option, #[serde(default)] pub not_before: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. @@ -470,6 +589,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit pub struct ApiGatewayV2httpRequestContextAuthentication { #[serde(default)] pub client_cert: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. @@ -487,6 +613,13 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { #[serde(rename = "subjectDN")] pub subject_dn: Option, pub validity: ApiGatewayV2httpRequestContextAuthenticationClientCertValidity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. @@ -497,6 +630,13 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { pub not_after: Option, #[serde(default)] pub not_before: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -520,6 +660,13 @@ pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -555,6 +702,13 @@ pub struct ApiGatewayV2CustomAuthorizerV1Request { #[serde(default)] pub stage_variables: HashMap, pub request_context: ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -590,6 +744,13 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub stage_variables: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerContext` represents the expected format of an API Gateway custom authorizer response. @@ -602,6 +763,13 @@ pub struct ApiGatewayCustomAuthorizerContext { pub num_key: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub bool_key: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. @@ -630,6 +798,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { #[serde(default)] #[serde(rename = "apiId")] pub apiid: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. @@ -643,6 +818,13 @@ pub struct ApiGatewayCustomAuthorizerRequest { /// nolint: stylecheck #[serde(default)] pub method_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. @@ -680,6 +862,13 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { #[serde(default)] pub stage_variables: HashMap, pub request_context: ApiGatewayCustomAuthorizerRequestTypeRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. @@ -696,6 +885,13 @@ where #[serde(bound = "", default)] pub context: T1, pub usage_identifier_key: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. @@ -709,6 +905,13 @@ where pub is_authorized: bool, #[serde(bound = "", default)] pub context: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -723,6 +926,13 @@ where pub policy_document: ApiGatewayCustomAuthorizerPolicy, #[serde(bound = "", default)] pub context: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy @@ -732,6 +942,13 @@ pub struct ApiGatewayCustomAuthorizerPolicy { #[serde(default)] pub version: Option, pub statement: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } fn default_http_method() -> Method { @@ -876,6 +1093,24 @@ mod test { assert!(output.contains(r#""headername":"headerValue2""#)); } + #[test] + #[cfg(all(feature = "apigw", feature = "catch-all-fields"))] + fn example_apigw_request_catch_all() { + use serde_json::json; + + let data = include_bytes!("../../fixtures/example-apigw-request-catch-all.json"); + let parsed: ApiGatewayProxyRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + + assert_eq!(parsed.other.get("otherField"), Some(&json!("foobar"))); + assert_eq!( + parsed.request_context.identity.other.get("otherField"), + Some(&json!(2345)) + ); + } + #[test] #[cfg(feature = "apigw")] fn example_apigw_restapi_openapi_request() { diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 4aa62e5b..ed68915e 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -17,6 +17,13 @@ where pub operation: AppSyncOperation, #[serde(bound = "")] pub payload: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. @@ -38,6 +45,13 @@ pub struct AppSyncIamIdentity { pub username: Option, #[serde(default)] pub user_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. @@ -61,6 +75,13 @@ where pub source_ip: Vec, #[serde(default)] pub default_auth_strategy: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type AppSyncOperation = String; @@ -72,6 +93,13 @@ pub struct AppSyncLambdaAuthorizerRequest { #[serde(default)] pub authorization_token: Option, pub request_context: AppSyncLambdaAuthorizerRequestContext, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered @@ -98,6 +126,13 @@ where #[serde(default)] #[serde(bound = "")] pub variables: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. @@ -115,6 +150,13 @@ where pub resolver_context: HashMap, pub denied_fields: Option>, pub ttl_override: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncDirectResolverEvent` represents the default payload structure sent by AWS AppSync @@ -147,6 +189,13 @@ where pub prev: Option, #[serde(bound = "")] pub stash: TStash, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncRequest` contains request-related metadata for a resolver invocation, @@ -160,6 +209,13 @@ pub struct AppSyncRequest { pub headers: HashMap>, #[serde(default)] pub domain_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. @@ -177,6 +233,13 @@ where pub field_name: String, #[serde(bound = "")] pub variables: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. @@ -187,6 +250,13 @@ where { #[serde(bound = "")] pub result: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIdentity` represents the identity of the caller as determined by the @@ -210,6 +280,13 @@ where pub claims: T, pub issuer: String, pub sub: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda @@ -221,6 +298,13 @@ where { #[serde(bound = "")] pub resolver_context: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 601e8774..1aeea5a2 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -41,6 +41,13 @@ where #[serde(default)] #[serde(bound = "")] pub detail: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index c1425b85..e7b4e69c 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from Agents for Amazon Bedrock. @@ -29,6 +31,13 @@ pub struct AgentEvent { pub session_attributes: HashMap, /// Contains prompt attributes and their values. pub prompt_session_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -36,6 +45,13 @@ pub struct AgentEvent { pub struct RequestBody { /// Contains the request body and its properties pub content: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -43,6 +59,13 @@ pub struct RequestBody { pub struct Content { /// The content of the request body pub properties: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -54,6 +77,13 @@ pub struct Property { pub r#type: String, /// The value of the parameter pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -65,6 +95,13 @@ pub struct Parameter { pub r#type: String, /// The value of the parameter pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -78,6 +115,13 @@ pub struct Agent { pub alias: String, /// The version of the agent. pub version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index 6581ed2c..d4bcd5f6 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -17,6 +19,13 @@ pub struct ChimeBotEvent { pub event_timestamp: DateTime, #[serde(rename = "Message")] pub message: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +37,13 @@ pub struct ChimeBotEventSender { #[serde(default)] #[serde(rename = "SenderIdType")] pub sender_id_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -39,6 +55,13 @@ pub struct ChimeBotEventDiscussion { #[serde(default)] #[serde(rename = "DiscussionType")] pub discussion_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -50,4 +73,11 @@ pub struct ChimeBotEventInboundHttpsEndpoint { #[serde(default)] #[serde(rename = "Url")] pub url: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 163abe72..e99d7c8c 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -29,6 +31,13 @@ pub struct ClientVpnConnectionHandlerRequest { #[serde(default)] #[serde(rename = "schema-version")] pub schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -43,6 +52,13 @@ pub struct ClientVpnConnectionHandlerResponse { #[serde(default)] #[serde(rename = "schema-version")] pub schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 44156b97..4134c144 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -35,6 +35,13 @@ where pub logical_resource_id: String, #[serde(bound = "")] pub resource_properties: P2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -57,6 +64,13 @@ where pub resource_properties: P2, #[serde(bound = "")] pub old_resource_properties: P1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -76,6 +90,13 @@ where pub physical_resource_id: String, #[serde(bound = "")] pub resource_properties: P2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -89,6 +110,13 @@ pub struct CloudFormationCustomResourceResponse { pub logical_resource_id: String, pub no_echo: bool, pub data: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index df5ba80a..786d6075 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -30,6 +30,7 @@ where { #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -46,6 +47,7 @@ where #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -58,6 +60,7 @@ where #[serde(flatten, bound = "")] pub common: CommonRequestParams, + // No `other` catch-all here; any additional fields will be caught in `common.other` instead } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] @@ -72,6 +75,13 @@ where pub resource_type: String, pub request_id: String, pub stack_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] @@ -84,6 +94,13 @@ where #[serde(bound = "")] pub data: D, pub no_echo: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index 720236c2..c3efebe3 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -33,6 +33,13 @@ where #[serde(default, bound = "")] pub alarm_data: CloudWatchAlarmData, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CloudWatchMetricAlarm` is the structure of an event triggered by CloudWatch metric alarms. @@ -59,6 +66,13 @@ where pub previous_state: Option>, #[serde(bound = "")] pub configuration: C, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -76,6 +90,13 @@ where pub timestamp: DateTime, pub actions_suppressed_by: Option, pub actions_suppressed_reason: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -85,6 +106,13 @@ pub struct CloudWatchMetricAlarmConfiguration { pub description: Option, #[serde(default)] pub metrics: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -94,6 +122,13 @@ pub struct CloudWatchMetricDefinition { #[serde(default)] pub return_data: bool, pub metric_stat: CloudWatchMetricStatDefinition, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -105,6 +140,13 @@ pub struct CloudWatchMetricStatDefinition { pub stat: Option, pub period: u16, pub metric: CloudWatchMetricStatMetricDefinition, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -114,6 +156,13 @@ pub struct CloudWatchMetricStatMetricDefinition { pub namespace: Option, pub name: String, pub dimensions: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -123,6 +172,13 @@ pub struct CloudWatchCompositeAlarmConfiguration { pub actions_suppressor: String, pub actions_suppressor_wait_period: u16, pub actions_suppressor_extension_period: u16, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -169,6 +225,13 @@ pub struct CloudWatchAlarmStateReasonDataMetric { pub threshold: f64, #[serde(default)] pub evaluated_datapoints: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -181,6 +244,13 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub value: Option, #[serde(default)] pub threshold: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -188,6 +258,13 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub struct ClodWatchAlarmStateReasonDataComposite { #[serde(default)] pub triggering_alarms: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -195,6 +272,13 @@ pub struct ClodWatchAlarmStateReasonDataComposite { pub struct CloudWatchAlarmStateTriggeringAlarm { pub arn: String, pub state: CloudWatchAlarmStateTriggeringAlarmState, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -203,6 +287,13 @@ pub struct CloudWatchAlarmStateTriggeringAlarmState { pub timestamp: DateTime, #[serde(default)] pub value: CloudWatchAlarmStateValue, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } impl<'de> Deserialize<'de> for CloudWatchAlarmStateReasonData { diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index fc332306..08b7500a 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -23,6 +23,13 @@ pub struct AWSAPICall { #[serde(rename = "eventID")] pub event_id: String, pub event_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,6 +40,13 @@ pub struct SessionIssuer { pub principal_id: String, pub arn: String, pub account_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -40,6 +54,13 @@ pub struct SessionIssuer { pub struct WebIdFederationData { pub federated_provider: Option, pub attributes: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -47,6 +68,13 @@ pub struct WebIdFederationData { pub struct Attributes { pub mfa_authenticated: String, pub creation_date: DateTime, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +85,13 @@ pub struct SessionContext { pub attributes: Attributes, pub source_identity: Option, pub ec2_role_delivery: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -64,6 +99,13 @@ pub struct SessionContext { pub struct OnBehalfOf { pub user_id: String, pub identity_store_arn: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } // https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html @@ -79,6 +121,13 @@ pub struct UserIdentity { pub session_context: Option, pub user_name: Option, pub on_behalf_of: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index 1bd44297..b52528ca 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct StateChangeNotification { pub deployment_id: String, pub state: String, pub deployment_group: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -21,6 +30,13 @@ pub struct DeploymentStateChangeNotification { pub deployment_id: String, pub instance_group_id: String, pub deployment_group: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -31,4 +47,11 @@ pub struct InstanceStateChangeNotification { pub state: String, #[serde(rename = "execution-id")] pub execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index ce5fa47c..e99798e7 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct PipelineExecutionStateChange { pub state: String, #[serde(rename = "execution-id")] pub execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct StageExecutionStateChange { pub execution_id: String, pub stage: String, pub state: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -34,6 +50,13 @@ pub struct ActionExecutionStateChange { pub region: String, #[serde(rename = "type")] pub type_field: ActionExecutionStateChangeType, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -43,4 +66,11 @@ pub struct ActionExecutionStateChangeType { pub category: String, pub provider: String, pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c8eb7834..c77fab4f 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -6,4 +8,11 @@ pub struct InstanceStateChange { #[serde(rename = "instance-id")] pub instance_id: String, pub state: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 87fb8085..4aed4818 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct AutoScalingPolicyStateChange { pub state: String, pub message: String, pub scaling_resource_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct ClusterStateChange { pub cluster_id: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -34,6 +50,13 @@ pub struct InstanceGroupStateChange { pub running_instance_count: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -46,4 +69,11 @@ pub struct StepStatusChange { pub cluster_id: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index fb5c50a7..009f1056 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; @@ -9,6 +11,13 @@ pub struct MatchmakingSearching { pub estimated_wait_millis: String, pub r#type: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -17,6 +26,13 @@ pub struct Ticket { pub ticket_id: String, pub start_time: String, pub players: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -27,12 +43,26 @@ pub struct Player { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub accepted: bool, pub player_session_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GameSessionInfo { pub players: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -45,6 +75,13 @@ pub struct PotentialMatchCreated { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -53,6 +90,13 @@ pub struct RuleEvaluationMetric { pub rule_name: String, pub passed_count: i64, pub failed_count: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -62,6 +106,13 @@ pub struct AcceptMatch { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -72,6 +123,13 @@ pub struct AcceptMatchCompleted { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -81,6 +139,13 @@ pub struct MatchmakingSucceeded { pub r#type: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,6 +157,13 @@ pub struct MatchmakingTimedOut { pub r#type: String, pub message: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -103,6 +175,13 @@ pub struct MatchmakingCancelled { pub r#type: String, pub message: String, pub game_session_info: GameSessionInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -115,4 +194,11 @@ pub struct MatchmakingFailed { pub message: String, pub game_session_info: GameSessionInfo, pub match_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index 08f05929..21669098 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -8,6 +10,13 @@ pub struct JobRunStateChange { pub state: String, pub job_run_id: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -18,6 +27,13 @@ pub struct CrawlerStarted { pub start_time: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -38,6 +54,13 @@ pub struct CrawlerSucceeded { pub state: String, pub partitions_created: String, pub cloud_watch_log_link: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -49,6 +72,13 @@ pub struct CrawlerFailed { pub cloud_watch_log_link: String, pub state: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -61,6 +91,13 @@ pub struct JobRunStatus { pub job_run_id: String, pub message: String, pub started_on: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -68,6 +105,13 @@ pub struct JobRunStatus { pub struct NotificationCondition { #[serde(rename = "NotifyDelayAfter")] pub notify_delay_after: f64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -77,6 +121,13 @@ pub struct DataCatalogTableStateChange { pub changed_partitions: Vec, pub type_of_change: String, pub table_name: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -85,4 +136,11 @@ pub struct DataCatalogDatabaseStateChange { pub database_name: String, pub type_of_change: String, pub changed_tables: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index 2a6a82e3..c6d8cca1 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -12,6 +14,13 @@ pub struct Event { pub end_time: String, pub event_description: Vec, pub affected_entities: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -19,6 +28,13 @@ pub struct Event { pub struct EventDescription { pub language: String, pub latest_description: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -26,4 +42,11 @@ pub struct EventDescription { pub struct Entity { pub entity_value: String, pub tags: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index 74a76e70..2a0041de 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -1,8 +1,17 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CMKEvent { #[serde(rename = "key-id")] pub key_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index 37d18d7a..a652b8b2 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -1,4 +1,7 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -18,6 +21,13 @@ pub struct Alert { pub created_at: String, pub actor: String, pub summary: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type BucketScanAlert = Alert; @@ -35,6 +45,13 @@ pub struct Trigger { pub created_at: String, pub description: String, pub risk: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +74,13 @@ pub struct BucketScanSummary { #[serde(rename = "Events")] pub events: HashMap, pub recipient_account_id: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -68,6 +92,13 @@ pub struct Ip { pub n34_205_153_2: i64, #[serde(rename = "72.21.196.70")] pub n72_21_196_70: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -76,6 +107,13 @@ pub struct TimeRange { pub count: i64, pub start: String, pub end: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -83,6 +121,13 @@ pub struct TimeRange { pub struct Location { #[serde(rename = "us-east-1")] pub us_east_1: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -92,6 +137,13 @@ pub struct ActionInfo { #[serde(rename = "ISP")] pub isp: HashMap, pub error_code: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -109,6 +161,13 @@ pub struct BucketWritableSummary { pub event_count: i64, #[serde(rename = "Timestamps")] pub timestamps: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -116,6 +175,13 @@ pub struct BucketWritableSummary { pub struct Bucket { #[serde(rename = "secret-bucket-name")] pub secret_bucket_name: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -123,6 +189,13 @@ pub struct Bucket { pub struct Acl { #[serde(rename = "secret-bucket-name")] pub secret_bucket_name: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -132,6 +205,13 @@ pub struct SecretBucketName { pub owner: Owner, #[serde(rename = "Grants")] pub grants: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -141,6 +221,13 @@ pub struct Owner { pub display_name: String, #[serde(rename = "ID")] pub id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -150,6 +237,13 @@ pub struct Grant { pub grantee: Grantee, #[serde(rename = "Permission")] pub permission: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -158,6 +252,13 @@ pub struct Grantee { pub r#type: String, #[serde(rename = "URI")] pub uri: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -179,6 +280,13 @@ pub struct BucketContainsHighRiskObjectSummary { pub owner: HashMap, #[serde(rename = "Timestamps")] pub timestamps: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -197,6 +305,13 @@ pub struct AlertUpdated { pub created_at: String, pub actor: String, pub trigger: UpdatedTrigger, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -205,6 +320,13 @@ pub struct UpdatedTrigger { #[serde(rename = "alert-type")] pub alert_type: String, pub features: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -218,4 +340,11 @@ pub struct FeatureInfo { #[serde(rename = "excession_times")] pub excession_times: Vec, pub risk: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index 2af343de..fd680c01 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -46,4 +46,11 @@ where pub resources: Vec, #[serde(bound = "")] pub detail: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index d1c192e5..30818648 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -15,6 +17,13 @@ pub struct InstanceStateChange { #[serde(rename = "ec2-instance-id")] pub ec2_instance_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -26,6 +35,13 @@ pub struct CommandStateChange { pub instance_id: String, pub r#type: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -40,6 +56,13 @@ pub struct DeploymentStateChange { pub deployment_id: String, pub command: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -51,4 +74,11 @@ pub struct Alert { pub instance_id: String, pub r#type: String, pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 1cd73e6e..2eb1ea5e 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -19,6 +19,13 @@ pub struct SignIn { #[serde(rename = "eventID")] pub event_id: String, pub event_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -28,6 +35,13 @@ pub struct UserIdentity { pub principal_id: String, pub arn: String, pub account_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -35,6 +49,13 @@ pub struct UserIdentity { pub struct ResponseElements { #[serde(rename = "ConsoleLogin")] pub console_login: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -46,4 +67,11 @@ pub struct AdditionalEventData { pub mobile_version: String, #[serde(rename = "MFAUsed")] pub mfaused: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 7d161822..6447887a 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -11,4 +13,11 @@ pub struct JobStateChange { #[serde(rename = "ami-id")] pub ami_id: Option, pub version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index fa6ffc3b..53a48e10 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -22,6 +24,13 @@ pub struct EC2AutomationStepStatusChange { pub step_name: String, #[serde(rename = "Action")] pub action: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -43,6 +52,13 @@ pub struct EC2AutomationExecutionStatusChange { pub time: f64, #[serde(rename = "ExecutedBy")] pub executed_by: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -51,6 +67,13 @@ pub struct StateChange { pub state: String, pub at_time: String, pub next_transition_time: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -69,6 +92,13 @@ pub struct ConfigurationComplianceStateChange { #[serde(rename = "patch-baseline-id")] pub patch_baseline_id: Option, pub serverity: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -79,6 +109,13 @@ pub struct MaintenanceWindowTargetRegistration { #[serde(rename = "window-id")] pub window_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -93,6 +130,13 @@ pub struct MaintenanceWindowExecutionStateChange { #[serde(rename = "window-execution-id")] pub window_execution_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -109,6 +153,13 @@ pub struct MaintenanceWindowTaskExecutionStateChange { #[serde(rename = "window-execution-id")] pub window_execution_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -129,6 +180,13 @@ pub struct MaintenanceWindowTaskTargetInvocationStateChange { pub status: String, #[serde(rename = "owner-information")] pub owner_information: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -137,6 +195,13 @@ pub struct MaintenanceWindowStateChange { #[serde(rename = "window-id")] pub window_id: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -146,6 +211,13 @@ pub struct ParameterStoreStateChange { pub name: String, pub r#type: String, pub description: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -161,6 +233,13 @@ pub struct EC2CommandStatusChange { #[serde(rename = "requested-date-time")] pub requested_date_time: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -175,6 +254,13 @@ pub struct EC2CommandInvocationStatusChange { #[serde(rename = "requested-date-time")] pub requested_date_time: String, pub status: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -204,6 +290,13 @@ pub struct EC2StateManagerAssociationStateChange { pub schedule_expression: String, #[serde(rename = "association-cwe-version")] pub association_cwe_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -235,4 +328,11 @@ pub struct EC2StateManagerInstanceAssociationStateChange { pub output_url: String, #[serde(rename = "instance-association-cwe-version")] pub instance_association_cwe_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index d5bc9681..08fbe24c 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use serde::{Deserialize, Serialize}; @@ -12,4 +14,11 @@ pub struct TagChangeOnResource { pub resource_type: String, pub version: i64, pub tags: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index 6a7e25d3..19f2aba0 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -12,4 +14,11 @@ pub struct CheckItemRefreshNotification { #[serde(rename = "resource_id")] pub resource_id: String, pub uuid: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 0c9ad4a8..1a485a06 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -3,6 +3,10 @@ use serde::{ ser::{Error as SeError, SerializeStruct}, Deserialize, Deserializer, Serialize, Serializer, }; +#[cfg(feature = "catch-all-fields")] +#[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] +use serde_json::Value; + use std::{fmt, io::BufReader}; /// `LogsEvent` represents the raw event sent by CloudWatch @@ -11,6 +15,13 @@ pub struct LogsEvent { /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer #[serde(rename = "awslogs")] pub aws_logs: AwsLogs, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event @@ -36,6 +47,13 @@ pub struct LogData { pub message_type: String, /// Entries in the log batch pub log_events: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LogEntry` represents a log entry from cloudwatch logs @@ -47,6 +65,13 @@ pub struct LogEntry { pub timestamp: i64, /// Message published in the application log pub message: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } impl<'de> Deserialize<'de> for AwsLogs { diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 7d5edfaa..81d85ef8 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; @@ -9,6 +11,13 @@ use crate::custom_serde::deserialize_nullish_boolean; pub struct CodeCommitEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type CodeCommitEventTime = DateTime; @@ -44,6 +53,13 @@ pub struct CodeCommitRecord { pub aws_region: Option, pub event_total_parts: u64, pub custom_data: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record @@ -51,6 +67,16 @@ pub struct CodeCommitRecord { #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { pub references: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeCommitReference` represents a Reference object in a CodeCommit object @@ -63,6 +89,13 @@ pub struct CodeCommitReference { pub ref_: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub created: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index ad7775d2..ac0a50de 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -44,6 +44,13 @@ pub struct CodeBuildEvent { /// Detail contains information specific to a build state-change or /// build phase-change event. pub detail: CodeBuildEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEventDetail` represents the all details related to the code build event @@ -84,6 +91,13 @@ pub struct CodeBuildEventDetail { #[serde(default)] #[serde(with = "codebuild_time::optional_time")] pub completed_phase_end: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event @@ -109,6 +123,13 @@ pub struct CodeBuildEventAdditionalInformation { pub source_version: Option, pub logs: CodeBuildLogs, pub phases: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildArtifact` represents the artifact provided to build @@ -123,6 +144,13 @@ pub struct CodeBuildArtifact { pub sha256_sum: Option, #[serde(default)] pub location: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEnvironment` represents the environment for a build @@ -140,6 +168,13 @@ pub struct CodeBuildEnvironment { pub type_: Option, #[serde(rename = "environment-variables")] pub environment_variables: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build @@ -155,6 +190,13 @@ pub struct CodeBuildEnvironmentVariable { /// Value is the value of the environment variable. #[serde(default)] pub value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildSource` represent the code source will be build @@ -165,6 +207,13 @@ pub struct CodeBuildSource { pub location: Option, #[serde(default)] pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildLogs` gives the log details of a code build @@ -180,6 +229,13 @@ pub struct CodeBuildLogs { #[serde(default)] #[serde(rename = "deep-link")] pub deep_link: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodeBuildPhase` represents the phase of a build and its details @@ -206,6 +262,13 @@ where pub phase_type: CodeBuildPhaseType, #[serde(rename = "phase-status")] pub phase_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type CodeBuildTime = DateTime; diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index b0a25c5b..ec6657b6 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; pub type CodeDeployDeploymentState = String; @@ -38,6 +40,13 @@ pub struct CodeDeployEvent { pub resources: Vec, /// Detail contains information specific to a deployment event. pub detail: CodeDeployEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -63,6 +72,13 @@ pub struct CodeDeployEventDetail { /// DeploymentGroup is the name of the deployment group. #[serde(default)] pub deployment_group: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] @@ -70,6 +86,13 @@ pub struct CodeDeployEventDetail { pub struct CodeDeployLifecycleEvent { pub deployment_id: String, pub lifecycle_event_hook_execution_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 6e394c17..1863e8aa 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; pub type CodePipelineStageState = String; @@ -42,6 +44,13 @@ pub struct CodePipelineCloudWatchEvent { pub resources: Vec, /// Detail contains information specific to a deployment event. pub detail: CodePipelineEventDetail, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -62,6 +71,13 @@ pub struct CodePipelineEventDetail { #[serde(default)] pub region: Option, pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -75,6 +91,13 @@ pub struct CodePipelineEventDetailType { pub provider: Option, /// From published EventBridge schema registry this is always int64 not string as documented pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 888e77b7..befb4c4c 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -6,6 +8,13 @@ use serde::{Deserialize, Serialize}; pub struct CodePipelineJobEvent { #[serde(rename = "CodePipeline.job")] pub code_pipeline_job: CodePipelineJob, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineJob` represents a job from an AWS CodePipeline event @@ -17,6 +26,13 @@ pub struct CodePipelineJob { #[serde(default)] pub account_id: Option, pub data: CodePipelineData, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineData` represents a job from an AWS CodePipeline event @@ -30,6 +46,13 @@ pub struct CodePipelineData { pub artifact_credentials: CodePipelineArtifactCredentials, #[serde(default)] pub continuation_token: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineActionConfiguration` represents an Action Configuration @@ -37,6 +60,13 @@ pub struct CodePipelineData { #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { pub configuration: CodePipelineConfiguration, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration @@ -49,6 +79,13 @@ pub struct CodePipelineConfiguration { #[serde(default)] #[serde(rename = "UserParameters")] pub user_parameters: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineInputArtifact` represents an input artifact @@ -59,6 +96,13 @@ pub struct CodePipelineInputArtifact { pub revision: Option, #[serde(default)] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineInputLocation` represents a input location @@ -69,6 +113,13 @@ pub struct CodePipelineInputLocation { #[serde(default)] #[serde(rename = "type")] pub location_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineS3Location` represents an s3 input location @@ -79,6 +130,13 @@ pub struct CodePipelineS3Location { pub bucket_name: Option, #[serde(default)] pub object_key: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineOutputArtifact` represents an output artifact @@ -89,6 +147,13 @@ pub struct CodePipelineOutputArtifact { pub revision: Option, #[serde(default)] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineOutputLocation` represents a output location @@ -99,6 +164,13 @@ pub struct CodePipelineOutputLocation { #[serde(default)] #[serde(rename = "type")] pub location_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials @@ -111,6 +183,13 @@ pub struct CodePipelineArtifactCredentials { pub session_token: Option, #[serde(default)] pub access_key_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index a0ebd8d9..315bc2ff 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -22,6 +22,13 @@ pub struct CognitoEvent { #[serde(default)] pub region: Option, pub version: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event @@ -34,6 +41,13 @@ pub struct CognitoDatasetRecord { pub old_value: Option, #[serde(default)] pub op: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreSignup` is sent by AWS Cognito User Pools when a user attempts to register @@ -46,6 +60,13 @@ pub struct CognitoEventUserPoolsPreSignup { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreSignupRequest, pub response: CognitoEventUserPoolsPreSignupResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -70,6 +91,13 @@ pub struct CognitoEventUserPoolsPreAuthentication { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreAuthenticationRequest, pub response: CognitoEventUserPoolsPreAuthenticationResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -95,6 +123,13 @@ where pub request: CognitoEventUserPoolsPostConfirmationRequest, #[serde(bound = "")] pub response: T, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -116,6 +151,13 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequest, pub response: CognitoEventUserPoolsPreTokenGenResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -144,6 +186,13 @@ pub struct CognitoEventUserPoolsPostAuthentication { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPostAuthenticationRequest, pub response: CognitoEventUserPoolsPostAuthenticationResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -165,6 +214,13 @@ pub struct CognitoEventUserPoolsMigrateUser { pub cognito_event_user_pools_migrate_user_request: CognitoEventUserPoolsMigrateUserRequest, #[serde(rename = "response")] pub cognito_event_user_pools_migrate_user_response: CognitoEventUserPoolsMigrateUserResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -185,6 +241,13 @@ pub struct CognitoEventUserPoolsCallerContext { pub awssdk_version: Option, #[serde(default)] pub client_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools @@ -202,6 +265,9 @@ pub struct CognitoEventUserPoolsHeader { pub caller_context: CognitoEventUserPoolsCallerContext, #[serde(default)] pub user_name: Option, + // no `other` catch-all, because this struct is itself #[serde(flatten)]-ed + // into a different struct that contains an `other` catch-all, so any + // additional fields will be caught there instead } /// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event @@ -217,6 +283,13 @@ pub struct CognitoEventUserPoolsPreSignupRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event @@ -226,6 +299,13 @@ pub struct CognitoEventUserPoolsPreSignupResponse { pub auto_confirm_user: bool, pub auto_verify_email: bool, pub auto_verify_phone: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event @@ -238,11 +318,26 @@ pub struct CognitoEventUserPoolsPreAuthenticationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub validation_data: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPreAuthenticationResponse {} +pub struct CognitoEventUserPoolsPreAuthenticationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -253,11 +348,26 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPostConfirmationResponse {} +pub struct CognitoEventUserPoolsPostConfirmationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -270,6 +380,13 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event @@ -277,6 +394,13 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponse { pub claims_override_details: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve @@ -289,6 +413,13 @@ pub struct CognitoEventUserPoolsPreTokenGenV2 { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsPreTokenGenRequestV2, pub response: CognitoEventUserPoolsPreTokenGenResponseV2, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event @@ -303,12 +434,26 @@ pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { #[serde(default)] pub client_metadata: HashMap, pub scopes: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { pub claims_and_scope_override_details: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token @@ -318,6 +463,13 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { pub group_override_details: GroupConfiguration, pub id_token_generation: Option, pub access_token_generation: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation @@ -326,6 +478,13 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { pub struct CognitoIdTokenGenerationV2 { pub claims_to_add_or_override: HashMap, pub claims_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation @@ -336,6 +495,13 @@ pub struct CognitoAccessTokenGenerationV2 { pub claims_to_suppress: Vec, pub scopes_to_add: Vec, pub scopes_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event @@ -349,11 +515,26 @@ pub struct CognitoEventUserPoolsPostAuthenticationRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] -pub struct CognitoEventUserPoolsPostAuthenticationResponse {} +pub struct CognitoEventUserPoolsPostAuthenticationResponse { + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, +} /// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -366,6 +547,13 @@ pub struct CognitoEventUserPoolsMigrateUserRequest { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event @@ -383,6 +571,13 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { pub desired_delivery_mediums: Option>, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub force_alias_creation: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ClaimsOverrideDetails` allows lambda to add, suppress or override claims in the token @@ -394,6 +589,13 @@ pub struct ClaimsOverrideDetails { #[serde(default)] pub claims_to_add_or_override: HashMap, pub claims_to_suppress: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role @@ -403,6 +605,13 @@ pub struct GroupConfiguration { pub groups_to_override: Vec, pub iam_roles_to_override: Vec, pub preferred_role: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication @@ -415,6 +624,13 @@ pub struct CognitoEventUserPoolsChallengeResult { pub challenge_result: bool, #[serde(default)] pub challenge_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters @@ -430,6 +646,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters @@ -442,6 +665,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { pub issue_tokens: bool, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub fail_authentication: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow @@ -454,6 +684,13 @@ pub struct CognitoEventUserPoolsDefineAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -478,6 +715,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters @@ -492,6 +736,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { pub private_challenge_parameters: HashMap, #[serde(default)] pub challenge_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user @@ -504,6 +755,13 @@ pub struct CognitoEventUserPoolsCreateAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -534,6 +792,13 @@ where pub client_metadata: HashMap, #[serde(default)] pub user_not_found: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters @@ -542,6 +807,13 @@ where pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub answer_correct: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsVerifyAuthChallenge` sent by AWS Cognito User Pools to verify if the response from the end user @@ -555,6 +827,13 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -574,6 +853,13 @@ pub struct CognitoEventUserPoolsCustomMessage { pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, pub request: CognitoEventUserPoolsCustomMessageRequest, pub response: CognitoEventUserPoolsCustomMessageResponse, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] @@ -614,6 +900,13 @@ where #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub client_metadata: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event @@ -626,6 +919,13 @@ pub struct CognitoEventUserPoolsCustomMessageResponse { pub email_message: Option, #[serde(default)] pub email_subject: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] @@ -855,7 +1155,8 @@ mod test { assert!(parsed.request.user_not_found); - let output: String = serde_json::to_string(&parsed).unwrap(); + let output: String = serde_json::to_string_pretty(&parsed).unwrap(); + println!("output is: {output}"); let reparsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(output.as_bytes()).unwrap(); assert_eq!(parsed, reparsed); } diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 7c06e13b..fd2631a6 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -34,6 +36,13 @@ pub struct ConfigEvent { pub rule_parameters: Option, #[serde(default)] pub version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 04f26eb5..c6bdb47b 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -13,6 +15,13 @@ pub struct ConnectEvent { #[serde(default)] #[serde(rename = "Name")] pub name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectDetails` holds the details of a Connect event @@ -26,6 +35,13 @@ pub struct ConnectDetails { #[serde(default)] #[serde(rename = "Parameters")] pub parameters: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. @@ -62,6 +78,13 @@ pub struct ConnectContactData { #[serde(default)] #[serde(rename = "InstanceARN")] pub instance_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectEndpoint` represents routing information. @@ -74,6 +97,13 @@ pub struct ConnectEndpoint { #[serde(default)] #[serde(rename = "Type")] pub type_: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ConnectQueue` represents a queue object. @@ -86,6 +116,13 @@ pub struct ConnectQueue { #[serde(default)] #[serde(rename = "ARN")] pub arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type ConnectResponse = HashMap; diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs index 5d1bdc19..d9ff1c4d 100644 --- a/lambda-events/src/event/documentdb/events/commom_types.rs +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -11,34 +11,76 @@ pub struct DatabaseCollection { db: String, #[serde(default)] coll: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentId { #[serde(rename = "_data")] pub data: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyIdOid { #[serde(rename = "$oid")] pub oid: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyId { #[serde(rename = "_id")] pub id: DocumentKeyIdOid, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct InnerTimestamp { t: usize, i: usize, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Timestamp { #[serde(rename = "$timestamp")] pub timestamp: InnerTimestamp, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs index 7761d62f..4085a88b 100644 --- a/lambda-events/src/event/documentdb/events/delete_event.rs +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeDeleteEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index 273c897c..fed75259 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; @@ -16,4 +18,11 @@ pub struct ChangeDropDatabaseEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index a6f92934..23a8d7a8 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -1,5 +1,7 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -15,4 +17,11 @@ pub struct ChangeDropEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index 2f4df397..aaf82ddc 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeInsertEvent { //operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs index 47469ff9..8e8ef930 100644 --- a/lambda-events/src/event/documentdb/events/invalidate_event.rs +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{DocumentId, Timestamp}; @@ -10,4 +12,11 @@ pub struct ChangeInvalidateEvent { #[serde(default)] cluster_time: Option, // operation_type: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs index 8bc250fb..f1761a2a 100644 --- a/lambda-events/src/event/documentdb/events/rename_event.rs +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; @@ -18,4 +20,11 @@ pub struct ChangeRenameEvent { #[serde(default)] txn_number: Option, to: DatabaseCollection, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index c253e272..b0245241 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -17,4 +19,11 @@ pub struct ChangeReplaceEvent { // operation_type: String, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 04369cf0..5d3795d0 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; @@ -7,6 +9,13 @@ use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentK pub struct UpdateTruncate { field: String, new_size: usize, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -15,6 +24,13 @@ pub struct UpdateDescription { removed_fields: Vec, truncated_arrays: Vec, updated_fields: AnyDocument, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,4 +49,11 @@ pub struct ChangeUpdateEvent { update_description: UpdateDescription, #[serde(default)] txn_number: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs index 67f7c9ad..fa753823 100644 --- a/lambda-events/src/event/documentdb/mod.rs +++ b/lambda-events/src/event/documentdb/mod.rs @@ -6,6 +6,8 @@ use self::events::{ replace_event::ChangeReplaceEvent, update_event::ChangeUpdateEvent, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(tag = "operationType", rename_all = "camelCase")] @@ -23,6 +25,13 @@ pub enum ChangeEvent { #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentDbInnerEvent { pub event: ChangeEvent, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,6 +42,13 @@ pub struct DocumentDbEvent { pub events: Vec, #[serde(default)] pub event_source: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 91380f82..2a99ed69 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -5,6 +5,8 @@ use crate::{ }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::fmt; #[cfg(test)] @@ -115,6 +117,13 @@ impl fmt::Display for KeyType { pub struct Event { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows @@ -128,6 +137,13 @@ pub struct TimeWindowEvent { #[serde(rename = "TimeWindowProperties")] #[serde(flatten)] pub time_window_properties: TimeWindowProperties, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. @@ -138,6 +154,13 @@ pub struct TimeWindowEventResponse { #[serde(flatten)] pub time_window_event_response_properties: TimeWindowEventResponseProperties, pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// EventRecord stores information about each record of a DynamoDb stream event @@ -198,6 +221,13 @@ pub struct EventRecord { /// The DynamoDB table that this event was recorded for. #[serde(default)] pub table_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -207,6 +237,13 @@ pub struct UserIdentity { pub type_: String, #[serde(default)] pub principal_id: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item @@ -248,6 +285,13 @@ pub struct StreamRecord { #[serde(default)] #[serde(rename = "StreamViewType")] pub stream_view_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index 5502e81a..f68b4e57 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -20,6 +22,13 @@ pub struct EcrScanEvent { #[serde(default)] pub account: Option, pub detail: EcrScanEventDetailType, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -38,6 +47,13 @@ pub struct EcrScanEventDetailType { pub image_digest: Option, #[serde(rename = "image-tags")] pub image_tags: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -61,6 +77,13 @@ pub struct EcrScanEventFindingSeverityCounts { #[serde(default)] #[serde(rename = "UNDEFINED")] pub undefined: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index 5ed14840..f2d86550 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -30,6 +30,13 @@ where pub resources: Option>, #[serde(bound = "")] pub detail: T1, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 6a0a13fd..195c6da9 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -3,6 +3,8 @@ use crate::{ encodings::{Base64Data, MillisecondTimestamp}, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. @@ -20,6 +22,13 @@ pub struct KinesisFirehoseEvent { #[serde(default)] pub region: Option, pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -31,12 +40,26 @@ pub struct KinesisFirehoseEventRecord { pub data: Base64Data, #[serde(rename = "kinesisRecordMetadata")] pub kinesis_firehose_record_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -49,6 +72,13 @@ pub struct KinesisFirehoseResponseRecord { pub result: Option, pub data: Base64Data, pub metadata: KinesisFirehoseResponseRecordMetadata, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -57,6 +87,13 @@ pub struct KinesisFirehoseResponseRecordMetadata { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub partition_keys: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -70,6 +107,13 @@ pub struct KinesisFirehoseRecordMetadata { pub sequence_number: Option, pub subsequence_number: i64, pub approximate_arrival_timestamp: MillisecondTimestamp, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 36f59c7b..f4301b1e 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::{borrow::Cow, collections::HashMap, fmt}; use serde::{ @@ -12,6 +14,13 @@ pub struct IamPolicyDocument { #[serde(default)] pub version: Option, pub statement: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource @@ -27,6 +36,13 @@ pub struct IamPolicyStatement { #[serde(default, deserialize_with = "deserialize_policy_condition")] #[serde(skip_serializing_if = "Option::is_none")] pub condition: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type IamPolicyCondition = HashMap>>; @@ -178,6 +194,8 @@ mod tests { effect: IamPolicyEffect::Allow, resource: vec!["some:resource".into()], condition: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; let policy_ser = serde_json::to_value(policy).unwrap(); diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index 07352120..cb262bd0 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -1,6 +1,8 @@ use crate::{custom_serde::serialize_headers, encodings::Base64Data, iam::IamPolicyDocument}; use http::HeaderMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See @@ -13,6 +15,13 @@ pub struct IoTCoreCustomAuthorizerRequest { pub protocols: Vec, pub protocol_data: Option, pub connection_metadata: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -21,6 +30,13 @@ pub struct IoTCoreProtocolData { pub tls: Option, pub http: Option, pub mqtt: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +44,13 @@ pub struct IoTCoreProtocolData { pub struct IoTCoreTlsContext { #[serde(default)] pub server_name: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -38,6 +61,13 @@ pub struct IoTCoreHttpContext { pub headers: HeaderMap, #[serde(default)] pub query_string: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -48,6 +78,13 @@ pub struct IoTCoreMqttContext { pub password: Base64Data, #[serde(default)] pub username: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -55,6 +92,13 @@ pub struct IoTCoreMqttContext { pub struct IoTCoreConnectionMetadata { #[serde(default)] pub id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. @@ -68,6 +112,13 @@ pub struct IoTCoreCustomAuthorizerResponse { pub disconnect_after_in_seconds: u32, pub refresh_after_in_seconds: u32, pub policy_documents: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index bf010b50..866918a9 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -11,12 +13,26 @@ pub struct IoTOneClickEvent { pub device_event: IoTOneClickDeviceEvent, pub device_info: IoTOneClickDeviceInfo, pub placement_info: IoTOneClickPlacementInfo, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { pub button_clicked: IoTOneClickButtonClicked, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -26,6 +42,13 @@ pub struct IoTOneClickButtonClicked { pub click_type: Option, #[serde(default)] pub reported_time: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -39,6 +62,13 @@ pub struct IoTOneClickDeviceInfo { #[serde(default)] pub device_id: Option, pub remaining_life: f64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -54,6 +84,13 @@ pub struct IoTOneClickPlacementInfo { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub devices: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 2d2e4627..00e4f6af 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct IoTButtonEvent { pub click_type: Option, #[serde(default)] pub battery_voltage: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 12c1df99..30475675 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -1,5 +1,7 @@ use crate::iot::*; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema @@ -14,6 +16,13 @@ pub struct IoTCustomAuthorizerRequest { pub authorization_token: Option, #[serde(default)] pub token_signature: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type IoTHttpContext = IoTCoreHttpContext; @@ -33,4 +42,11 @@ pub struct IoTCustomAuthorizerResponse { pub disconnect_after_in_seconds: i32, pub refresh_after_in_seconds: i32, pub policy_documents: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 27a1e921..032f1615 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -1,5 +1,7 @@ use crate::{custom_serde::deserialize_lambda_map, encodings::MillisecondTimestamp}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -14,6 +16,13 @@ pub struct KafkaEvent { pub records: HashMap>, #[serde(default)] pub bootstrap_servers: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -29,6 +38,13 @@ pub struct KafkaRecord { pub key: Option, pub value: Option, pub headers: Vec>>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index 74c95606..e9f8f676 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -1,5 +1,7 @@ use crate::encodings::Base64Data; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -9,6 +11,13 @@ pub struct KinesisAnalyticsOutputDeliveryEvent { #[serde(default)] pub application_arn: Option, pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -17,12 +26,26 @@ pub struct KinesisAnalyticsOutputDeliveryEventRecord { #[serde(default)] pub record_id: Option, pub data: Base64Data, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponse { pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -33,4 +56,11 @@ pub struct KinesisAnalyticsOutputDeliveryResponseRecord { /// possible values include Ok and DeliveryFailed #[serde(default)] pub result: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index a67da850..6b9c1c77 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -3,12 +3,21 @@ use crate::{ time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}, }; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows @@ -22,6 +31,13 @@ pub struct KinesisTimeWindowEvent { #[serde(rename = "TimeWindowProperties")] #[serde(flatten)] pub time_window_properties: TimeWindowProperties, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. @@ -32,6 +48,13 @@ pub struct KinesisTimeWindowEventResponse { #[serde(flatten)] pub time_window_event_response_properties: TimeWindowEventResponseProperties, // pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -57,6 +80,13 @@ pub struct KinesisEventRecord { #[serde(default)] pub invoke_identity_arn: Option, pub kinesis: KinesisRecord, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -72,6 +102,13 @@ pub struct KinesisRecord { pub sequence_number: String, #[serde(default)] pub kinesis_schema_version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index 37ddfe39..a754af0d 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -1,5 +1,7 @@ use http::HeaderMap; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; @@ -25,6 +27,13 @@ pub struct LambdaFunctionUrlRequest { pub request_context: LambdaFunctionUrlRequestContext, pub body: Option, pub is_base64_encoded: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. @@ -50,6 +59,13 @@ pub struct LambdaFunctionUrlRequestContext { pub time: Option, pub time_epoch: i64, pub http: LambdaFunctionUrlRequestContextHttpDescription, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. @@ -57,6 +73,13 @@ pub struct LambdaFunctionUrlRequestContext { #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { pub iam: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. @@ -73,6 +96,13 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { pub user_arn: Option, #[serde(default)] pub user_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. @@ -89,6 +119,13 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { pub source_ip: Option, #[serde(default)] pub user_agent: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. @@ -103,4 +140,11 @@ pub struct LambdaFunctionUrlResponse { pub body: Option, pub is_base64_encoded: bool, pub cookies: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index d8f9403c..c9c23c53 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -20,6 +22,13 @@ pub struct LexEvent { pub alternative_intents: Option>, /// Deprecated: the DialogAction field is never populated by Lex events pub dialog_action: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -28,6 +37,13 @@ pub struct LexBot { pub name: Option, pub alias: Option, pub version: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -40,6 +56,13 @@ pub struct LexCurrentIntent { #[serde(default)] pub slot_details: HashMap, pub confirmation_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] @@ -52,6 +75,13 @@ pub struct LexAlternativeIntents { #[serde(default)] pub slot_details: HashMap, pub confirmation_status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -59,6 +89,13 @@ pub struct LexAlternativeIntents { pub struct SlotDetail { pub resolutions: Option>>, pub original_value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -73,6 +110,13 @@ pub struct LexDialogAction { pub slots: Option, pub slot_to_elicit: Option, pub response_card: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type SessionAttributes = HashMap; @@ -84,6 +128,13 @@ pub type Slots = HashMap>; pub struct LexResponse { pub session_attributes: SessionAttributes, pub dialog_action: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -92,6 +143,13 @@ pub struct LexResponseCard { pub version: Option, pub content_type: Option, pub generic_attachments: Option>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -102,6 +160,13 @@ pub struct Attachment { pub image_url: Option, pub attachment_link_url: Option, pub buttons: Option>>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 23327276..fd9088c5 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -15,6 +15,13 @@ pub struct RabbitMqEvent { #[serde(default)] #[serde(rename = "rmqMessagesByQueue")] pub messages_by_queue: HashMap>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -24,6 +31,13 @@ pub struct RabbitMqMessage { #[serde(default)] pub data: Option, pub redelivered: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -56,6 +70,13 @@ where pub app_id: Option, pub cluster_id: Option, pub body_size: u64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index e3eb691e..133960f3 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -10,6 +12,13 @@ pub struct S3BatchJobEvent { pub invocation_id: Option, pub job: S3BatchJob, pub tasks: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJob` whichs have the job id @@ -18,6 +27,13 @@ pub struct S3BatchJobEvent { pub struct S3BatchJob { #[serde(default)] pub id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobTask` represents one task in the s3 batch job and have all task details @@ -32,6 +48,13 @@ pub struct S3BatchJobTask { pub s3_version_id: Option, #[serde(default)] pub s3_bucket_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobResponse` is the response of a iven s3 batch job with the results @@ -45,6 +68,13 @@ pub struct S3BatchJobResponse { #[serde(default)] pub invocation_id: Option, pub results: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3BatchJobResult` represents the result of a given task @@ -57,4 +87,11 @@ pub struct S3BatchJobResult { pub result_code: Option, #[serde(default)] pub result_string: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index e062c7d2..46a334db 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; @@ -10,6 +12,13 @@ use crate::custom_serde::deserialize_lambda_map; pub struct S3Event { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `S3EventRecord` which wrap record data @@ -32,6 +41,13 @@ pub struct S3EventRecord { #[serde(default)] pub response_elements: HashMap, pub s3: S3Entity, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -39,6 +55,13 @@ pub struct S3EventRecord { pub struct S3UserIdentity { #[serde(default)] pub principal_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -47,6 +70,13 @@ pub struct S3RequestParameters { #[serde(default)] #[serde(rename = "sourceIPAddress")] pub source_ip_address: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -59,6 +89,13 @@ pub struct S3Entity { pub configuration_id: Option, pub bucket: S3Bucket, pub object: S3Object, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -70,6 +107,13 @@ pub struct S3Bucket { pub owner_identity: Option, #[serde(default)] pub arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -86,6 +130,13 @@ pub struct S3Object { pub e_tag: Option, #[serde(default)] pub sequencer: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 1cd7b934..3b01fe73 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -24,6 +24,13 @@ where pub user_request: UserRequest, pub user_identity: UserIdentity, pub protocol_version: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `GetObjectContext` contains the input and output details @@ -34,6 +41,13 @@ pub struct GetObjectContext { pub input_s3_url: String, pub output_route: String, pub output_token: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `HeadObjectContext` @@ -42,6 +56,13 @@ pub struct GetObjectContext { #[serde(rename_all = "camelCase")] pub struct HeadObjectContext { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ListObjectsContext` @@ -50,6 +71,13 @@ pub struct HeadObjectContext { #[serde(rename_all = "camelCase")] pub struct ListObjectsContext { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `ListObjectsV2Context` @@ -58,6 +86,13 @@ pub struct ListObjectsContext { #[serde(rename_all = "camelCase")] pub struct ListObjectsV2Context { pub input_s3_url: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `Configuration` contains information about the Object Lambda access point @@ -72,6 +107,13 @@ where pub supporting_access_point_arn: String, #[serde(default, bound = "")] pub payload: P, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `UserRequest` contains information about the original call to S3 Object Lambda @@ -82,6 +124,13 @@ pub struct UserRequest { #[serde(deserialize_with = "deserialize_headers", default)] #[serde(serialize_with = "serialize_headers")] pub headers: HeaderMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda @@ -94,6 +143,13 @@ pub struct UserIdentity { pub account_id: String, pub access_key_id: String, pub session_context: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -102,6 +158,13 @@ pub struct SessionContext { pub attributes: HashMap, #[serde(default)] pub session_issuer: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -112,6 +175,13 @@ pub struct SessionIssuer { pub arn: String, pub account_id: String, pub user_name: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index 3ed8d238..2c5b497c 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -1,4 +1,6 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] @@ -6,6 +8,13 @@ pub struct SecretsManagerSecretRotationEvent { pub step: String, pub secret_id: String, pub client_request_token: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 2a60957a..78262b0b 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -7,6 +9,13 @@ use serde::{Deserialize, Serialize}; pub struct SimpleEmailEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -17,6 +26,13 @@ pub struct SimpleEmailRecord { #[serde(default)] pub event_source: Option, pub ses: SimpleEmailService, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -25,6 +41,13 @@ pub struct SimpleEmailService { pub mail: SimpleEmailMessage, pub receipt: SimpleEmailReceipt, pub content: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -39,6 +62,13 @@ pub struct SimpleEmailMessage { pub headers_truncated: bool, #[serde(default)] pub message_id: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -55,6 +85,13 @@ pub struct SimpleEmailReceipt { pub virus_verdict: SimpleEmailVerdict, pub action: SimpleEmailReceiptAction, pub processing_time_millis: i64, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -64,6 +101,13 @@ pub struct SimpleEmailHeader { pub name: Option, #[serde(default)] pub value: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -79,6 +123,13 @@ pub struct SimpleEmailCommonHeaders { pub date: Option, #[serde(default)] pub subject: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SimpleEmailReceiptAction` is a logical union of fields present in all action @@ -100,6 +151,13 @@ pub struct SimpleEmailReceiptAction { pub invocation_type: Option, pub function_arn: Option, pub organization_arn: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] @@ -107,6 +165,13 @@ pub struct SimpleEmailReceiptAction { pub struct SimpleEmailVerdict { #[serde(default)] pub status: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } pub type SimpleEmailDispositionValue = String; @@ -116,6 +181,13 @@ pub type SimpleEmailDispositionValue = String; #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { pub disposition: SimpleEmailDispositionValue, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 0fda569d..f68ec984 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -1,5 +1,7 @@ use chrono::{DateTime, Utc}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; @@ -11,6 +13,14 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; #[serde(rename_all = "PascalCase")] pub struct SnsEvent { pub records: Vec, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// SnsRecord stores information about each record of a SNS event @@ -28,6 +38,14 @@ pub struct SnsRecord { /// An SNS object representing the SNS message. pub sns: SnsMessage, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// SnsMessage stores information about each record of a SNS event @@ -76,6 +94,14 @@ pub struct SnsMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An alternate `Event` notification event to use alongside `SnsRecordObj` and `SnsMessageObj` if you want to deserialize an object inside your SNS messages rather than getting an `Option` message @@ -86,6 +112,14 @@ pub struct SnsMessage { #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsEventObj { pub records: Vec>, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) @@ -104,6 +138,14 @@ pub struct SnsRecordObj { /// An SNS object representing the SNS message. pub sns: SnsMessageObj, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` @@ -156,6 +198,14 @@ pub struct SnsMessageObj { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Structured metadata items (such as timestamps, geospatial data, signatures, and identifiers) about the message. @@ -172,6 +222,14 @@ pub struct MessageAttribute { /// The user-specified message attribute value. #[serde(rename = "Value")] pub value: String, + + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -188,6 +246,13 @@ pub struct CloudWatchAlarmPayload { pub alarm_arn: String, pub old_state_value: String, pub trigger: CloudWatchAlarmTrigger, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -208,6 +273,13 @@ pub struct CloudWatchAlarmTrigger { pub unit: Option, #[serde(default)] pub dimensions: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -220,6 +292,13 @@ pub struct CloudWatchMetricDataQuery { pub period: Option, #[serde(default, deserialize_with = "deserialize_nullish_boolean")] pub return_data: bool, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -229,6 +308,13 @@ pub struct CloudWatchMetricStat { pub period: i64, pub stat: String, pub unit: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] @@ -238,12 +324,26 @@ pub struct CloudWatchMetric { pub dimensions: Vec, pub metric_name: Option, pub namespace: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CloudWatchDimension { pub name: String, pub value: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 563dda1a..64a368ca 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -1,5 +1,7 @@ use crate::{custom_serde::deserialize_lambda_map, encodings::Base64Data}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages @@ -8,6 +10,13 @@ use std::collections::HashMap; pub struct SqsEvent { #[serde(rename = "Records")] pub records: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An individual SQS Message, its metadata, and Message Attributes @@ -38,6 +47,13 @@ pub struct SqsMessage { pub event_source: Option, #[serde(default)] pub aws_region: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SqsEvent` to be used alongside `SqsMessageObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string @@ -48,6 +64,13 @@ pub struct SqsEventObj { #[serde(rename = "Records")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub records: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to `SqsMessage` to be used alongside `SqsEventObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string @@ -83,6 +106,13 @@ pub struct SqsMessageObj { pub event_source: Option, #[serde(default)] pub aws_region: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] @@ -96,18 +126,39 @@ pub struct SqsMessageAttribute { pub binary_list_values: Vec, #[serde(default)] pub data_type: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BatchItemFailure { pub item_identifier: String, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages @@ -117,6 +168,13 @@ pub struct BatchItemFailure { pub struct SqsApiEventObj { #[serde(bound(deserialize = "T: DeserializeOwned"))] pub messages: Vec>, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages @@ -124,6 +182,13 @@ pub struct SqsApiEventObj { #[serde(rename_all = "camelCase")] pub struct SqsApiEvent { pub messages: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to @@ -153,6 +218,13 @@ pub struct SqsApiMessageObj { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// An individual SQS API Message, its metadata, and Message Attributes @@ -176,6 +248,13 @@ pub struct SqsApiMessage { #[serde(deserialize_with = "deserialize_lambda_map")] #[serde(default)] pub message_attributes: HashMap, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } #[cfg(test)] diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 9e0fd76f..673217fc 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -1,10 +1,19 @@ use serde::{Deserialize, Serialize}; +#[cfg(feature = "catch-all-fields")] +use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `KinesisBatchItemFailure` is the individual record which failed processing. @@ -13,6 +22,13 @@ pub struct KinesisEventResponse { pub struct KinesisBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. @@ -20,6 +36,13 @@ pub struct KinesisBatchItemFailure { #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. @@ -28,6 +51,13 @@ pub struct DynamoDbEventResponse { pub struct DynamoDbBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. @@ -35,6 +65,13 @@ pub struct DynamoDbBatchItemFailure { #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { pub batch_item_failures: Vec, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } /// `SqsBatchItemFailure` is the individual record which failed processing. @@ -43,4 +80,11 @@ pub struct SqsEventResponse { pub struct SqsBatchItemFailure { #[serde(default)] pub item_identifier: Option, + /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. + /// Enabled with Cargo feature `catch-all-fields`. + /// If `catch-all-fields` is disabled, any additional fields that are present will be ignored. + #[cfg(feature = "catch-all-fields")] + #[cfg_attr(docsrs, doc(cfg(feature = "catch-all-fields")))] + #[serde(flatten)] + pub other: serde_json::Map, } diff --git a/lambda-events/src/fixtures/example-apigw-request-catch-all.json b/lambda-events/src/fixtures/example-apigw-request-catch-all.json new file mode 100644 index 00000000..fe1955f4 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request-catch-all.json @@ -0,0 +1,137 @@ +{ + "resource": "/{proxy+}", + "path": "/hello/world", + "httpMethod": "POST", + "headers": { + "Accept": "*/*", + "Accept-Encoding": "gzip, deflate", + "cache-control": "no-cache", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Desktop-Viewer": "true", + "CloudFront-Is-Mobile-Viewer": "false", + "CloudFront-Is-SmartTV-Viewer": "false", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Viewer-Country": "US", + "Content-Type": "application/json", + "headerName": "headerValue", + "Host": "gy415nuibc.execute-api.us-east-1.amazonaws.com", + "Postman-Token": "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f", + "User-Agent": "PostmanRuntime/2.4.5", + "Via": "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==", + "X-Forwarded-For": "54.240.196.186, 54.182.214.83", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": [ + "*/*" + ], + "Accept-Encoding": [ + "gzip, deflate" + ], + "cache-control": [ + "no-cache" + ], + "CloudFront-Forwarded-Proto": [ + "https" + ], + "CloudFront-Is-Desktop-Viewer": [ + "true" + ], + "CloudFront-Is-Mobile-Viewer": [ + "false" + ], + "CloudFront-Is-SmartTV-Viewer": [ + "false" + ], + "CloudFront-Is-Tablet-Viewer": [ + "false" + ], + "CloudFront-Viewer-Country": [ + "US" + ], + "Content-Type": [ + "application/json" + ], + "headerName": [ + "headerValue" + ], + "Host": [ + "gy415nuibc.execute-api.us-east-1.amazonaws.com" + ], + "Postman-Token": [ + "9f583ef0-ed83-4a38-aef3-eb9ce3f7a57f" + ], + "User-Agent": [ + "PostmanRuntime/2.4.5" + ], + "Via": [ + "1.1 d98420743a69852491bbdea73f7680bd.cloudfront.net (CloudFront)" + ], + "X-Amz-Cf-Id": [ + "pn-PWIJc6thYnZm5P0NMgOUglL1DYtl0gdeJky8tqsg8iS_sgsKD1A==" + ], + "X-Forwarded-For": [ + "54.240.196.186, 54.182.214.83" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "queryStringParameters": { + "name": "me" + }, + "multiValueQueryStringParameters": { + "name": [ + "me" + ] + }, + "pathParameters": { + "proxy": "hello/world" + }, + "stageVariables": { + "stageVariableName": "stageVariableValue" + }, + "requestContext": { + "accountId": "12345678912", + "resourceId": "roq9wj", + "path": "/hello/world", + "stage": "testStage", + "domainName": "gy415nuibc.execute-api.us-east-2.amazonaws.com", + "domainPrefix": "y0ne18dixk", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "protocol": "HTTP/1.1", + "identity": { + "cognitoIdentityPoolId": "theCognitoIdentityPoolId", + "accountId": "theAccountId", + "cognitoIdentityId": "theCognitoIdentityId", + "caller": "theCaller", + "apiKey": "theApiKey", + "apiKeyId": "theApiKeyId", + "accessKey": "ANEXAMPLEOFACCESSKEY", + "sourceIp": "192.168.196.186", + "cognitoAuthenticationType": "theCognitoAuthenticationType", + "cognitoAuthenticationProvider": "theCognitoAuthenticationProvider", + "userArn": "theUserArn", + "userAgent": "PostmanRuntime/2.4.5", + "user": "theUser", + "otherField": 2345 + }, + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "resourcePath": "/{proxy+}", + "httpMethod": "POST", + "requestTime": "15/May/2020:06:01:09 +0000", + "requestTimeEpoch": 1589522469693, + "apiId": "gy415nuibc" + }, + "body": "{\r\n\t\"a\": 1\r\n}", + "otherField": "foobar" +} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4498e275..6788f285 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -23,6 +23,7 @@ apigw_http = [] apigw_websockets = [] alb = [] pass_through = [] +catch-all-fields = ["aws_lambda_events/catch-all-fields"] tracing = ["lambda_runtime/tracing"] # enables access to the Tracing utilities opentelemetry = ["lambda_runtime/opentelemetry"] # enables access to the OpenTelemetry layers and utilities anyhow = ["lambda_runtime/anyhow"] # enables From for Diagnostic for anyhow error types, see README.md for more info @@ -49,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.16.0" +version = "0.17.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index fa8953f2..8b868d25 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -77,6 +77,9 @@ impl LambdaResponse { // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { @@ -101,6 +104,9 @@ impl LambdaResponse { // are combined with commas and included in the headers field. headers, multi_value_headers: HeaderMap::new(), + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }) } #[cfg(feature = "alb")] @@ -118,6 +124,9 @@ impl LambdaResponse { status_code, parts.status.canonical_reason().unwrap_or_default() )), + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "apigw_websockets")] RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { @@ -128,6 +137,9 @@ impl LambdaResponse { // "multi_value_headers" fields together resulting in duplicate response headers. headers: HeaderMap::new(), multi_value_headers: headers, + // Today, this implementation doesn't provide any additional fields + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }), #[cfg(feature = "pass_through")] RequestOrigin::PassThrough => { diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 0a9836c4..85d5fca6 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -21,6 +21,9 @@ serde = { version = "1.0.204", features = ["derive"] } [dev-dependencies] reqwest = { version = "0.12.5", features = ["blocking"] } +[features] +catch-all-fields = ["aws_lambda_events/catch-all-fields"] + [[bin]] name = "helloworld" path = "src/helloworld.rs" diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs index 41ddd2d8..b8dc3782 100644 --- a/lambda-integration-tests/src/authorizer.rs +++ b/lambda-integration-tests/src/authorizer.rs @@ -39,15 +39,21 @@ fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { resource: vec![method_arn.to_owned()], effect: aws_lambda_events::iam::IamPolicyEffect::Allow, condition: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; let policy = ApiGatewayCustomAuthorizerPolicy { version: Some("2012-10-17".to_string()), statement: vec![stmt], + #[cfg(feature = "catch-all-fields")] + other: Default::default(), }; ApiGatewayCustomAuthorizerResponse { principal_id: Some("user".to_owned()), policy_document: policy, context: json!({ "hello": "world" }), usage_identifier_key: None, + #[cfg(feature = "catch-all-fields")] + other: Default::default(), } } diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index b40cd0c6..c3a74f8c 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -22,6 +22,8 @@ async fn func(_event: LambdaEvent) -> Result Date: Thu, 17 Jul 2025 17:17:11 -0700 Subject: [PATCH 197/211] chore(lambda-extension, lambda-http, lambda-runtime-api-client, lambda-runtime): bump patch versions for new release (#1020) --- lambda-extension/Cargo.toml | 4 ++-- lambda-http/Cargo.toml | 6 +++--- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index d178520a..515a97fe 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda-extension" -version = "0.12.1" +version = "0.12.2" edition = "2021" rust-version = "1.81.0" authors = [ @@ -25,7 +25,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true, features = ["http1", "client", "server"] } hyper-util = { workspace = true } -lambda_runtime_api_client = { version = "0.12", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } serde = { version = "1", features = ["derive"] } serde_json = "^1" tokio = { version = "1.0", features = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 6788f285..9787f302 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.1" +version = "0.15.2" authors = [ "David Calavera ", "Harold Sun ", @@ -38,7 +38,7 @@ http = { workspace = true } http-body = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } -lambda_runtime = { version = "0.14.2", path = "../lambda-runtime" } +lambda_runtime = { version = "0.14.3", path = "../lambda-runtime" } mime = "0.3" percent-encoding = "2.2" pin-project-lite = { workspace = true } @@ -57,7 +57,7 @@ features = ["alb", "apigw"] [dev-dependencies] axum-core = "0.5.0" axum-extra = { version = "0.10.0", features = ["query"] } -lambda_runtime_api_client = { version = "0.12.1", path = "../lambda-runtime-api-client" } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" tokio = { version = "1.0", features = ["macros"] } diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index cc2289af..dcc2916d 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.2" +version = "0.12.3" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 6e0dde73..3daefc11 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.2" +version = "0.14.3" authors = [ "David Calavera ", "Harold Sun ", @@ -37,8 +37,8 @@ http = { workspace = true } http-body-util = { workspace = true } http-serde = { workspace = true } hyper = { workspace = true, features = ["http1", "client"] } -lambda-extension = { version = "0.12.1", path = "../lambda-extension", default-features = false, optional = true } -lambda_runtime_api_client = { version = "0.12.2", path = "../lambda-runtime-api-client", default-features = false } +lambda-extension = { version = "0.12.2", path = "../lambda-extension", default-features = false, optional = true } +lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } pin-project = "1" From 1447217b54a4cecf567ba6feb8a83bdd8e355d17 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:17:50 -0700 Subject: [PATCH 198/211] chore: (lambda-http, lambda-extension): add missing docsrs feature annotations (#1021) --- lambda-extension/src/lib.rs | 1 + lambda-http/src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/lambda-extension/src/lib.rs b/lambda-extension/src/lib.rs index a27635b8..b6aec18f 100644 --- a/lambda-extension/src/lib.rs +++ b/lambda-extension/src/lib.rs @@ -26,6 +26,7 @@ pub mod requests; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub use lambda_runtime_api_client::tracing; /// Execute the given events processor diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index cea99750..33ccea12 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -68,6 +68,7 @@ extern crate maplit; pub use http::{self, Response}; /// Utilities to initialize and use `tracing` and `tracing-subscriber` in Lambda Functions. #[cfg(feature = "tracing")] +#[cfg_attr(docsrs, doc(cfg(feature = "tracing")))] pub use lambda_runtime::tracing; use lambda_runtime::Diagnostic; pub use lambda_runtime::{self, service_fn, tower, Context, Error, LambdaEvent, Service}; From baf3653402930d74500dde0e8a2d2264407d3f4f Mon Sep 17 00:00:00 2001 From: Jonathan Lim Date: Tue, 22 Jul 2025 13:03:24 -0700 Subject: [PATCH 199/211] fix(lambda-events): derive Default on various Events (#1022) * fix(lambda-events): derive Default on various events * fix(lambda-events): format new code --- lambda-events/src/encodings/time.rs | 4 ++-- lambda-events/src/event/activemq/mod.rs | 6 ++--- lambda-events/src/event/autoscaling/mod.rs | 2 +- lambda-events/src/event/chime_bot/mod.rs | 8 +++---- lambda-events/src/event/cloudformation/mod.rs | 13 ++++++++--- .../src/event/cloudformation/provider.rs | 12 +++++++--- .../src/event/cloudwatch_events/mod.rs | 2 +- lambda-events/src/event/code_commit/mod.rs | 2 +- lambda-events/src/event/codebuild/mod.rs | 14 ++++++------ lambda-events/src/event/codedeploy/mod.rs | 6 ++--- .../src/event/codepipeline_cloudwatch/mod.rs | 6 ++--- .../src/event/codepipeline_job/mod.rs | 22 +++++++++---------- lambda-events/src/event/config/mod.rs | 2 +- lambda-events/src/event/connect/mod.rs | 10 ++++----- lambda-events/src/event/dynamodb/mod.rs | 6 ++--- lambda-events/src/event/ecr_scan/mod.rs | 6 ++--- lambda-events/src/event/firehose/mod.rs | 4 ++-- lambda-events/src/event/iot_1_click/mod.rs | 10 ++++----- lambda-events/src/event/iot_button/mod.rs | 2 +- lambda-events/src/event/kafka/mod.rs | 4 ++-- lambda-events/src/event/kinesis/event.rs | 2 +- lambda-events/src/event/lex/mod.rs | 10 ++++----- lambda-events/src/event/rabbitmq/mod.rs | 2 +- lambda-events/src/event/secretsmanager/mod.rs | 2 +- lambda-events/src/event/ses/mod.rs | 2 +- lambda-events/src/event/sns/mod.rs | 6 ++--- 26 files changed, 89 insertions(+), 76 deletions(-) diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index d0d9526e..c7ca04a6 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -7,7 +7,7 @@ use serde::{ use std::ops::{Deref, DerefMut}; /// Timestamp with millisecond precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MillisecondTimestamp( #[serde(deserialize_with = "deserialize_milliseconds")] #[serde(serialize_with = "serialize_milliseconds")] @@ -73,7 +73,7 @@ impl DerefMut for SecondDuration { } /// Duration with minute precision. -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] #[serde(serialize_with = "serialize_duration_minutes")] diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index d9283bea..4bced0ab 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { #[serde(default)] @@ -22,7 +22,7 @@ pub struct ActiveMqEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqMessage { #[serde(default)] @@ -59,7 +59,7 @@ pub struct ActiveMqMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqDestination { #[serde(default)] diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index 1aeea5a2..cbcde746 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingEvent where diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index d4bcd5f6..42b9ef0e 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEvent { #[serde(rename = "Sender")] @@ -28,7 +28,7 @@ pub struct ChimeBotEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventSender { #[serde(default)] @@ -46,7 +46,7 @@ pub struct ChimeBotEventSender { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventDiscussion { #[serde(default)] @@ -64,7 +64,7 @@ pub struct ChimeBotEventDiscussion { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventInboundHttpsEndpoint { #[serde(default)] diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 4134c144..995ab846 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -19,7 +19,13 @@ where Delete(DeleteRequest), } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +impl Default for CloudFormationCustomResourceRequest { + fn default() -> Self { + CloudFormationCustomResourceRequest::Create(CreateRequest::default()) + } +} + +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest where @@ -99,7 +105,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse { pub status: CloudFormationCustomResourceResponseStatus, @@ -119,9 +125,10 @@ pub struct CloudFormationCustomResourceResponse { pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudFormationCustomResourceResponseStatus { + #[default] Success, Failed, } diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index 786d6075..71277388 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -22,7 +22,13 @@ where Delete(DeleteRequest), } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +impl Default for CloudFormationCustomResourceRequest { + fn default() -> Self { + CloudFormationCustomResourceRequest::Create(CreateRequest::default()) + } +} + +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest where @@ -63,7 +69,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CommonRequestParams where @@ -84,7 +90,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize, Default)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse where diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index fd680c01..c865b0e0 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -21,7 +21,7 @@ pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. /// For examples of events that come via CloudWatch Events, see -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent where diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 81d85ef8..e35ab093 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -6,7 +6,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; /// `CodeCommitEvent` represents a CodeCommit event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index ac0a50de..4ffb821d 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -12,7 +12,7 @@ pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: /// -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { /// AccountID is the id of the AWS account from which the event originated. @@ -54,7 +54,7 @@ pub struct CodeBuildEvent { } /// `CodeBuildEventDetail` represents the all details related to the code build event -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventDetail { #[serde(rename = "build-status")] @@ -101,7 +101,7 @@ pub struct CodeBuildEventDetail { } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventAdditionalInformation { pub artifact: CodeBuildArtifact, @@ -133,7 +133,7 @@ pub struct CodeBuildEventAdditionalInformation { } /// `CodeBuildArtifact` represents the artifact provided to build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildArtifact { #[serde(default)] @@ -154,7 +154,7 @@ pub struct CodeBuildArtifact { } /// `CodeBuildEnvironment` represents the environment for a build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironment { #[serde(default)] @@ -200,7 +200,7 @@ pub struct CodeBuildEnvironmentVariable { } /// `CodeBuildSource` represent the code source will be build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildSource { #[serde(default)] @@ -217,7 +217,7 @@ pub struct CodeBuildSource { } /// `CodeBuildLogs` gives the log details of a code build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildLogs { #[serde(default)] diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index ec6657b6..85649729 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -7,7 +7,7 @@ pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: /// -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { /// AccountID is the id of the AWS account from which the event originated. @@ -49,7 +49,7 @@ pub struct CodeDeployEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEventDetail { /// InstanceGroupID is the ID of the instance group. @@ -81,7 +81,7 @@ pub struct CodeDeployEventDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Deserialize, Serialize, Eq, PartialEq)] +#[derive(Debug, Default, Clone, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct CodeDeployLifecycleEvent { pub deployment_id: String, diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 1863e8aa..3bcc5f2b 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -11,7 +11,7 @@ pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: /// -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { /// Version is the version of the event's schema. @@ -53,7 +53,7 @@ pub struct CodePipelineCloudWatchEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetail { #[serde(default)] @@ -80,7 +80,7 @@ pub struct CodePipelineEventDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetailType { #[serde(default)] diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index befb4c4c..41a9966e 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJobEvent { #[serde(rename = "CodePipeline.job")] @@ -18,7 +18,7 @@ pub struct CodePipelineJobEvent { } /// `CodePipelineJob` represents a job from an AWS CodePipeline event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJob { #[serde(default)] @@ -36,7 +36,7 @@ pub struct CodePipelineJob { } /// `CodePipelineData` represents a job from an AWS CodePipeline event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineData { pub action_configuration: CodePipelineActionConfiguration, @@ -56,7 +56,7 @@ pub struct CodePipelineData { } /// `CodePipelineActionConfiguration` represents an Action Configuration -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { pub configuration: CodePipelineConfiguration, @@ -70,7 +70,7 @@ pub struct CodePipelineActionConfiguration { } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineConfiguration { #[serde(default)] @@ -89,7 +89,7 @@ pub struct CodePipelineConfiguration { } /// `CodePipelineInputArtifact` represents an input artifact -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputArtifact { pub location: CodePipelineInputLocation, @@ -106,7 +106,7 @@ pub struct CodePipelineInputArtifact { } /// `CodePipelineInputLocation` represents a input location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputLocation { pub s3_location: CodePipelineS3Location, @@ -123,7 +123,7 @@ pub struct CodePipelineInputLocation { } /// `CodePipelineS3Location` represents an s3 input location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineS3Location { #[serde(default)] @@ -140,7 +140,7 @@ pub struct CodePipelineS3Location { } /// `CodePipelineOutputArtifact` represents an output artifact -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputArtifact { pub location: CodePipelineInputLocation, @@ -157,7 +157,7 @@ pub struct CodePipelineOutputArtifact { } /// `CodePipelineOutputLocation` represents a output location -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputLocation { pub s3_location: CodePipelineS3Location, @@ -174,7 +174,7 @@ pub struct CodePipelineOutputLocation { } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineArtifactCredentials { #[serde(default)] diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index fd2631a6..981419d8 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigEvent { /// The ID of the AWS account that owns the rule diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index c6bdb47b..3f15ce0c 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -6,7 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `ConnectEvent` contains the data structure for a Connect event. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEvent { #[serde(rename = "Details")] @@ -25,7 +25,7 @@ pub struct ConnectEvent { } /// `ConnectDetails` holds the details of a Connect event -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectDetails { #[serde(rename = "ContactData")] @@ -45,7 +45,7 @@ pub struct ConnectDetails { } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectContactData { /// The custom attributes from Connect that the Lambda function was invoked with. @@ -88,7 +88,7 @@ pub struct ConnectContactData { } /// `ConnectEndpoint` represents routing information. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEndpoint { #[serde(default)] @@ -107,7 +107,7 @@ pub struct ConnectEndpoint { } /// `ConnectQueue` represents a queue object. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectQueue { #[serde(default)] diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 2a99ed69..3e1b3ab3 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -113,7 +113,7 @@ impl fmt::Display for KeyType { /// The `Event` stream event handled to Lambda /// -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] pub records: Vec, @@ -164,7 +164,7 @@ pub struct TimeWindowEventResponse { } /// EventRecord stores information about each record of a DynamoDb stream event -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventRecord { /// The region in which the GetRecords request was received. @@ -248,7 +248,7 @@ pub struct UserIdentity { /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item /// in a DynamoDB table. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StreamRecord { /// The approximate date and time when the stream record was created, in UNIX diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index f68b4e57..e3ff7ff8 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { #[serde(default)] @@ -31,7 +31,7 @@ pub struct EcrScanEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventDetailType { #[serde(default)] @@ -56,7 +56,7 @@ pub struct EcrScanEventDetailType { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { #[serde(default)] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 195c6da9..8bce49ac 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -8,7 +8,7 @@ use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEvent { #[serde(default)] @@ -31,7 +31,7 @@ pub struct KinesisFirehoseEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEventRecord { #[serde(default)] diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 866918a9..50338120 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -7,7 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickEvent { pub device_event: IoTOneClickDeviceEvent, @@ -22,7 +22,7 @@ pub struct IoTOneClickEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { pub button_clicked: IoTOneClickButtonClicked, @@ -35,7 +35,7 @@ pub struct IoTOneClickDeviceEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickButtonClicked { #[serde(default)] @@ -51,7 +51,7 @@ pub struct IoTOneClickButtonClicked { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceInfo { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -71,7 +71,7 @@ pub struct IoTOneClickDeviceInfo { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickPlacementInfo { #[serde(default)] diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 00e4f6af..9a7aaec3 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { #[serde(default)] diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 032f1615..7332b1e0 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaEvent { #[serde(default)] @@ -25,7 +25,7 @@ pub struct KafkaEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaRecord { #[serde(default)] diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 6b9c1c77..6bfb2bea 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index c9c23c53..6a458c8a 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { pub message_version: Option, @@ -46,7 +46,7 @@ pub struct LexBot { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexCurrentIntent { pub name: Option, @@ -65,7 +65,7 @@ pub struct LexCurrentIntent { pub other: serde_json::Map, } -#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexAlternativeIntents { pub name: Option, @@ -98,7 +98,7 @@ pub struct SlotDetail { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexDialogAction { pub type_: Option, @@ -137,7 +137,7 @@ pub struct LexResponse { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponseCard { pub version: Option, diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index fd9088c5..6c79e2b0 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { #[serde(default)] diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index 2c5b497c..fc883e52 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SecretsManagerSecretRotationEvent { pub step: String, diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 78262b0b..9358135d 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailEvent { #[serde(rename = "Records")] diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index f68ec984..611a16b7 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -9,7 +9,7 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsEvent { pub records: Vec, @@ -24,7 +24,7 @@ pub struct SnsEvent { } /// SnsRecord stores information about each record of a SNS event -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsRecord { /// A string containing the event source. @@ -49,7 +49,7 @@ pub struct SnsRecord { } /// SnsMessage stores information about each record of a SNS event -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsMessage { /// The type of SNS message. For a lambda event, this should always be **Notification** From aff8d883c62997ef2615714dce9f7ddfd557147d Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Wed, 30 Jul 2025 18:13:04 -0700 Subject: [PATCH 200/211] fix(semver): bump lamda-http to `0.16.0` due to breaking change in dependency, `aws_lambda_events` (#1025) --- lambda-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 9787f302..b930afdb 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.15.2" +version = "0.16.0" authors = [ "David Calavera ", "Harold Sun ", From 685e81c5d4d0c11f3f816c8f540b196c27173210 Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Tue, 5 Aug 2025 08:18:31 -0700 Subject: [PATCH 201/211] chore(ci): unpin cargo lambda in integration test ci (#1026) --- .github/workflows/run-integration-test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/run-integration-test.yml b/.github/workflows/run-integration-test.yml index 03065d9e..a4fd604b 100644 --- a/.github/workflows/run-integration-test.yml +++ b/.github/workflows/run-integration-test.yml @@ -18,8 +18,6 @@ jobs: repo: cargo-lambda/cargo-lambda platform: linux arch: x86_64 - # TODO: unpin once https://github.com/awslabs/aws-lambda-rust-runtime/issues/1006 is fixed - tag: v1.8.1 - name: install Zig toolchain uses: mlugg/setup-zig@v2 with: From 42e2c1f656e9a4fd5d438b04bcbcbd95c9cf4d48 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Tue, 19 Aug 2025 09:47:38 +0200 Subject: [PATCH 202/211] Fix CI checks (#1030) * remove unused * update link --- lambda-runtime-api-client/src/tracing.rs | 2 +- lambda-runtime/src/requests.rs | 14 -------------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lambda-runtime-api-client/src/tracing.rs b/lambda-runtime-api-client/src/tracing.rs index 097e8dcf..5aa9bfa1 100644 --- a/lambda-runtime-api-client/src/tracing.rs +++ b/lambda-runtime-api-client/src/tracing.rs @@ -44,7 +44,7 @@ pub fn init_default_subscriber() { /// a lot of async concurrency. Since, writing to STDOUT can briefly block your tokio runtime - ref [tracing #2653](https://github.com/tokio-rs/tracing/issues/2653). /// In that case, you might prefer to use [tracing_appender::NonBlocking](https://docs.rs/tracing-appender/latest/tracing_appender/non_blocking/struct.NonBlocking.html) instead - particularly if your Lambda is fairly long-running and stays warm. /// Though, note that you are then responsible -/// for ensuring gracefuls shutdown. See [`examples/graceful-shutdown`] for a complete example. +/// for ensuring gracefuls shutdown. See [aws-samples/graceful-shutdown-with-aws-lambda](https://github.com/aws-samples/graceful-shutdown-with-aws-lambda) for a complete example. /// /// This function uses environment variables set with [Lambda's advanced logging controls](https://aws.amazon.com/blogs/compute/introducing-advanced-logging-controls-for-aws-lambda-functions/) /// if they're configured for your function. diff --git a/lambda-runtime/src/requests.rs b/lambda-runtime/src/requests.rs index e8b0183c..ec1e6ae1 100644 --- a/lambda-runtime/src/requests.rs +++ b/lambda-runtime/src/requests.rs @@ -24,20 +24,6 @@ impl IntoRequest for NextEventRequest { } } -#[derive(Debug, Eq, PartialEq)] -pub struct NextEventResponse<'a> { - // lambda-runtime-aws-request-id - pub request_id: &'a str, - // lambda-runtime-deadline-ms - pub deadline: u64, - // lambda-runtime-invoked-function-arn - pub arn: &'a str, - // lambda-runtime-trace-id - pub trace_id: &'a str, - // the actual body, - pub body: Vec, -} - // /runtime/invocation/{AwsRequestId}/response pub(crate) struct EventCompletionRequest<'a, R, B, S, D, E> where From 92a80b49958b0a8d2d14223279b08d0b3b9d3147 Mon Sep 17 00:00:00 2001 From: Nick Angelou Date: Thu, 21 Aug 2025 06:53:40 +0200 Subject: [PATCH 203/211] Expose streaming API (#1013) * Expose streaming API --- examples/http-axum-streaming-otel/Cargo.toml | 20 ++ examples/http-axum-streaming-otel/README.md | 25 +++ examples/http-axum-streaming-otel/src/main.rs | 106 +++++++++ examples/http-axum-streaming/Cargo.toml | 14 ++ examples/http-axum-streaming/README.md | 20 ++ examples/http-axum-streaming/src/main.rs | 70 ++++++ lambda-http/src/lib.rs | 2 +- lambda-http/src/streaming.rs | 201 +++++++++++++++--- 8 files changed, 422 insertions(+), 36 deletions(-) create mode 100644 examples/http-axum-streaming-otel/Cargo.toml create mode 100644 examples/http-axum-streaming-otel/README.md create mode 100644 examples/http-axum-streaming-otel/src/main.rs create mode 100644 examples/http-axum-streaming/Cargo.toml create mode 100644 examples/http-axum-streaming/README.md create mode 100644 examples/http-axum-streaming/src/main.rs diff --git a/examples/http-axum-streaming-otel/Cargo.toml b/examples/http-axum-streaming-otel/Cargo.toml new file mode 100644 index 00000000..d917bb03 --- /dev/null +++ b/examples/http-axum-streaming-otel/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "http-axum-streaming-otel" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +bytes = "1" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_http", "tracing", "opentelemetry" +] } +opentelemetry = "0.30" +opentelemetry_sdk = { version = "0.30", features = ["rt-tokio"] } +opentelemetry-stdout = { version = "0.30", features = ["trace"] } +thiserror = "2.0" +tokio = { version = "1", features = ["macros"] } +tokio-stream = "0.1.2" +tracing = "0.1" +tracing-opentelemetry = "0.31" +tracing-subscriber = "0.3" diff --git a/examples/http-axum-streaming-otel/README.md b/examples/http-axum-streaming-otel/README.md new file mode 100644 index 00000000..194fe4e4 --- /dev/null +++ b/examples/http-axum-streaming-otel/README.md @@ -0,0 +1,25 @@ +# AWS Lambda Function example + +This example shows how to build a **streaming HTTP response** with `Axum` and +run it on AWS Lambda using a custom runtime with OpenTelemetry (OTel) support. + +Tracing data is exported as console log entries visible in CloudWatch. Note that +CloudWatch assigns a `Timestamp` to each log entry based on when it receives the +data (batch exported). To see when work actually occurred, look at the span's +event attributes, which include the precise local timestamps of those events. + +## Build & Deploy + +1. Install + [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with: + - `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words +4. Enable Lambda streaming response on Lambda console: change the function url's + invoke mode to `RESPONSE_STREAM` +5. Verify the function works: `curl -N `. The results should be + streamed back with 0.5 second pause between each word. + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-streaming-otel/src/main.rs b/examples/http-axum-streaming-otel/src/main.rs new file mode 100644 index 00000000..64f4e49e --- /dev/null +++ b/examples/http-axum-streaming-otel/src/main.rs @@ -0,0 +1,106 @@ +//! # Example: Axum Streaming Responses on AWS Lambda with OTel +//! +//! Demonstrates serving **incremental streaming responses** from Axum handlers +//! running in AWS Lambda using a **custom** `lambda_runtime::Runtime` with +//! OpenTelemetry (OTel) support. +//! +//! - Runs with a custom `Runtime` + `StreamAdapter`, which convert Axum +//! responses into streaming bodies delivered as data is produced (unlike the +//! default `run_with_streaming_response` helper). + +use axum::{ + body::Body, + http::{ + self, + header::{CACHE_CONTROL, CONTENT_TYPE}, + StatusCode, + }, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use bytes::Bytes; +use core::{convert::Infallible, time::Duration}; +use lambda_http::{ + lambda_runtime::{ + layers::{OpenTelemetryFaasTrigger, OpenTelemetryLayer as OtelLayer}, + tracing::Instrument, + Runtime, + }, + tracing, Error, StreamAdapter, +}; +use opentelemetry::trace::TracerProvider; +use opentelemetry_sdk::trace; +use thiserror::Error; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; +use tracing_subscriber::prelude::*; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("{0}")] + Http(#[from] http::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } +} + +#[tracing::instrument(skip_all)] +async fn stream_words() -> Result { + let (tx, rx) = mpsc::channel::>(8); + let body = Body::from_stream(ReceiverStream::new(rx)); + + tokio::spawn( + async move { + for (idx, msg) in ["Hello", "world", "from", "Lambda!"].iter().enumerate() { + tokio::time::sleep(Duration::from_millis(500)).await; + let line = format!("{msg}\n"); + tracing::info!(chunk.idx = idx, bytes = line.len(), "emit"); + if tx.send(Ok(Bytes::from(line))).await.is_err() { + break; + } + } + } + .instrument(tracing::info_span!("producer.stream_words")), + ); + + Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/plain; charset=utf-8") + .header(CACHE_CONTROL, "no-cache") + .body(body)?) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + // Set up OpenTelemetry tracer provider that writes spans to stdout for + // debugging purposes + let exporter = opentelemetry_stdout::SpanExporter::default(); + let tracer_provider = trace::SdkTracerProvider::builder() + .with_batch_exporter(exporter) + .build(); + + // Set up link between OpenTelemetry and tracing crate + tracing_subscriber::registry() + .with(tracing_opentelemetry::OpenTelemetryLayer::new( + tracer_provider.tracer("my-streaming-app"), + )) + .init(); + + let svc = Router::new().route("/", get(stream_words)); + + // Initialize the Lambda runtime and add OpenTelemetry tracing + let runtime = Runtime::new(StreamAdapter::from(svc)).layer( + OtelLayer::new(|| { + if let Err(err) = tracer_provider.force_flush() { + eprintln!("Error flushing traces: {err:#?}"); + } + }) + .with_trigger(OpenTelemetryFaasTrigger::Http), + ); + + runtime.run().await +} diff --git a/examples/http-axum-streaming/Cargo.toml b/examples/http-axum-streaming/Cargo.toml new file mode 100644 index 00000000..a951562b --- /dev/null +++ b/examples/http-axum-streaming/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-axum-streaming" +version = "0.1.0" +edition = "2021" + +[dependencies] +axum = "0.8" +bytes = "1" +lambda_http = { path = "../../lambda-http", default-features = false, features = [ + "apigw_http", "tracing" +] } +thiserror = "2.0" +tokio = { version = "1", features = ["macros"] } +tokio-stream = "0.1.2" diff --git a/examples/http-axum-streaming/README.md b/examples/http-axum-streaming/README.md new file mode 100644 index 00000000..fe7e573d --- /dev/null +++ b/examples/http-axum-streaming/README.md @@ -0,0 +1,20 @@ +# AWS Lambda Function example + +This example demonstrates building a **streaming** HTTP response with Axum, +deployed on AWS Lambda using a custom runtime. + +## Build & Deploy + +1. Install + [cargo-lambda](https://github.com/cargo-lambda/cargo-lambda#installation) +2. Build the function with `cargo lambda build --release` +3. Deploy the function to AWS Lambda with: + - `cargo lambda deploy --enable-function-url --iam-role YOUR_ROLE` to stream words +4. Enable Lambda streaming response on Lambda console: change the function url's + invoke mode to `RESPONSE_STREAM` +5. Verify the function works: `curl -N `. The results should be + streamed back with 0.5 second pause between each word. + +## Build for ARM 64 + +Build the function with `cargo lambda build --release --arm64` diff --git a/examples/http-axum-streaming/src/main.rs b/examples/http-axum-streaming/src/main.rs new file mode 100644 index 00000000..1812f879 --- /dev/null +++ b/examples/http-axum-streaming/src/main.rs @@ -0,0 +1,70 @@ +//! # Example: Axum Streaming Responses on AWS Lambda +//! +//! Demonstrates serving **incremental streaming responses** from Axum handlers +//! running in AWS Lambda. +//! +//! - Runs with `run_with_streaming_response`, which uses the **default Lambda +//! runtime** to convert Axum responses into streaming bodies delivered as +//! data is produced (unlike the OTel example, which used a custom `Runtime` + +//! `StreamAdapter`). + +use axum::{ + body::Body, + http::{ + self, + header::{CACHE_CONTROL, CONTENT_TYPE}, + StatusCode, + }, + response::{IntoResponse, Response}, + routing::get, + Router, +}; +use bytes::Bytes; +use core::{convert::Infallible, time::Duration}; +use lambda_http::{run_with_streaming_response, tracing, Error}; +use thiserror::Error; +use tokio::sync::mpsc; +use tokio_stream::wrappers::ReceiverStream; + +#[derive(Debug, Error)] +pub enum AppError { + #[error("{0}")] + Http(#[from] http::Error), +} + +impl IntoResponse for AppError { + fn into_response(self) -> Response { + (StatusCode::INTERNAL_SERVER_ERROR, self.to_string()).into_response() + } +} + +async fn stream_words() -> Result { + let (tx, rx) = mpsc::channel::>(8); + let body = Body::from_stream(ReceiverStream::new(rx)); + + tokio::spawn(async move { + for msg in ["Hello", "world", "from", "Lambda!"] { + tokio::time::sleep(Duration::from_millis(500)).await; + if tx.send(Ok(Bytes::from(format!("{msg}\n")))).await.is_err() { + break; + } + } + }); + + Ok(Response::builder() + .status(StatusCode::OK) + .header(CONTENT_TYPE, "text/plain; charset=utf-8") + .header(CACHE_CONTROL, "no-cache") + .body(body)?) +} + +#[tokio::main] +async fn main() -> Result<(), Error> { + tracing::init_default_subscriber(); + + let svc = Router::new().route("/", get(stream_words)); + + // Automatically convert the service into a streaming response with a + // default runtime. + run_with_streaming_response(svc).await +} diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 33ccea12..36e2ffbd 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -102,7 +102,7 @@ use std::{ }; mod streaming; -pub use streaming::run_with_streaming_response; +pub use streaming::{run_with_streaming_response, StreamAdapter}; /// Type alias for `http::Request`s with a fixed [`Body`](enum.Body.html) type pub type Request = http::Request; diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index a93408b4..6dd17230 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -1,22 +1,88 @@ -use crate::{http::header::SET_COOKIE, request::LambdaRequest, tower::ServiceBuilder, Request, RequestExt}; +use crate::{http::header::SET_COOKIE, request::LambdaRequest, Request, RequestExt}; use bytes::Bytes; -pub use http::{self, Response}; -use http_body::Body; -use lambda_runtime::Diagnostic; -pub use lambda_runtime::{self, tower::ServiceExt, Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; -use std::{ +use core::{ fmt::Debug, pin::Pin, task::{Context, Poll}, }; -use tokio_stream::Stream; +use futures_util::{Stream, TryFutureExt}; +pub use http::{self, Response}; +use http_body::Body; +use lambda_runtime::{ + tower::{ + util::{MapRequest, MapResponse}, + ServiceBuilder, ServiceExt, + }, + Diagnostic, +}; +pub use lambda_runtime::{Error, LambdaEvent, MetadataPrelude, Service, StreamResponse}; +use std::{future::Future, marker::PhantomData}; + +/// An adapter that lifts a standard [`Service`] into a +/// [`Service>`] which produces streaming Lambda HTTP +/// responses. +pub struct StreamAdapter<'a, S, B> { + service: S, + _phantom_data: PhantomData<&'a B>, +} + +impl<'a, S, B, E> From for StreamAdapter<'a, S, B> +where + S: Service, Error = E>, + S::Future: Send + 'a, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + fn from(service: S) -> Self { + StreamAdapter { + service, + _phantom_data: PhantomData, + } + } +} + +impl<'a, S, B, E> Service> for StreamAdapter<'a, S, B> +where + S: Service, Error = E>, + S::Future: Send + 'a, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + type Response = StreamResponse>; + type Error = E; + type Future = Pin> + Send + 'a>>; -/// Starts the Lambda Rust runtime and stream response back [Configure Lambda -/// Streaming Response](https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html). + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx) + } + + fn call(&mut self, req: LambdaEvent) -> Self::Future { + let event: Request = req.payload.into(); + Box::pin( + self.service + .call(event.with_lambda_context(req.context)) + .map_ok(into_stream_response), + ) + } +} + +/// Builds a streaming-aware Tower service from a `Service` **without** +/// boxing its future (no heap allocation / vtable). /// -/// This takes care of transforming the LambdaEvent into a [`Request`] and -/// accepts [`http::Response`] as response. -pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), Error> +/// Transforms `LambdaEvent` into `Request` with Lambda context +/// and wraps `Response` into `StreamResponse>`. +/// +/// Used internally by [`run_with_streaming_response`]; not part of the public +/// API. +#[allow(clippy::type_complexity)] +fn into_stream_service<'a, S, B, E>( + handler: S, +) -> MapResponse< + MapRequest) -> Request>, + impl FnOnce(Response) -> StreamResponse> + Clone, +> where S: Service, Error = E>, S::Future: Send + 'a, @@ -25,38 +91,59 @@ where B::Data: Into + Send, B::Error: Into + Send + Debug, { - let svc = ServiceBuilder::new() + ServiceBuilder::new() .map_request(|req: LambdaEvent| { let event: Request = req.payload.into(); event.with_lambda_context(req.context) }) .service(handler) - .map_response(|res| { - let (parts, body) = res.into_parts(); - - let mut prelude_headers = parts.headers; - - let cookies = prelude_headers.get_all(SET_COOKIE); - let cookies = cookies - .iter() - .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) - .collect::>(); + .map_response(into_stream_response) +} - prelude_headers.remove(SET_COOKIE); +/// Converts an `http::Response` into a streaming Lambda response. +fn into_stream_response(res: Response) -> StreamResponse> +where + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + let (parts, body) = res.into_parts(); - let metadata_prelude = MetadataPrelude { - headers: prelude_headers, - status_code: parts.status, - cookies, - }; + let mut headers = parts.headers; + let cookies = headers + .get_all(SET_COOKIE) + .iter() + .map(|c| String::from_utf8_lossy(c.as_bytes()).to_string()) + .collect::>(); + headers.remove(SET_COOKIE); - StreamResponse { - metadata_prelude, - stream: BodyStream { body }, - } - }); + StreamResponse { + metadata_prelude: MetadataPrelude { + headers, + status_code: parts.status, + cookies, + }, + stream: BodyStream { body }, + } +} - lambda_runtime::run(svc).await +/// Runs the Lambda runtime with a handler that returns **streaming** HTTP +/// responses. +/// +/// See the [AWS docs for response streaming]. +/// +/// [AWS docs for response streaming]: +/// https://docs.aws.amazon.com/lambda/latest/dg/configuration-response-streaming.html +pub async fn run_with_streaming_response<'a, S, B, E>(handler: S) -> Result<(), Error> +where + S: Service, Error = E>, + S::Future: Send + 'a, + E: Debug + Into, + B: Body + Unpin + Send + 'static, + B::Data: Into + Send, + B::Error: Into + Send + Debug, +{ + lambda_runtime::run(into_stream_service(handler)).await } pin_project_lite::pin_project! { @@ -85,3 +172,47 @@ where } } } + +#[cfg(test)] +mod test_stream_adapter { + use super::*; + + use crate::Body; + use http::StatusCode; + + // A middleware that logs requests before forwarding them to another service + struct LogService { + inner: S, + } + + impl Service> for LogService + where + S: Service>, + { + type Response = S::Response; + type Error = S::Error; + type Future = S::Future; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner.poll_ready(cx) + } + + fn call(&mut self, event: LambdaEvent) -> Self::Future { + println!("Lambda event: {event:#?}"); + self.inner.call(event) + } + } + + #[test] + fn stream_adapter_is_boxable() { + // Works with a concrete service stack (no boxing) + let svc = ServiceBuilder::new() + .layer_fn(|service| LogService { inner: service }) + .layer_fn(StreamAdapter::from) + .service_fn( + |_req: Request| async move { http::Response::builder().status(StatusCode::OK).body(Body::Empty) }, + ); + // Also works when the stack is boxed (type-erased) + let _boxed_svc = svc.boxed(); + } +} From b627d760a62c3d96c9edd3dab2d708eabd1b1aed Mon Sep 17 00:00:00 2001 From: DiscreteTom Date: Wed, 27 Aug 2025 04:51:11 +0800 Subject: [PATCH 204/211] fix: serialize `AlbTargetGroupRequest::query_string_parameters` value to string (#955) * fix: serialize `AlbTargetGroupRequest::query_string_parameters` value to string fix #954 * 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 | 18 +++++- 3 files changed, 85 insertions(+), 1 deletion(-) 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 aca3cd6c..02b50c78 100644 --- a/lambda-events/src/custom_serde/mod.rs +++ b/lambda-events/src/custom_serde/mod.rs @@ -34,6 +34,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 55e427e2..5ec4d242 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, }; @@ -19,6 +20,7 @@ pub struct AlbTargetGroupRequest { #[serde(default)] pub path: Option, #[serde(default)] + #[serde(serialize_with = "serialize_query_string_parameters")] pub query_string_parameters: QueryMap, #[serde(default)] pub multi_value_query_string_parameters: QueryMap, @@ -100,6 +102,7 @@ pub struct AlbTargetGroupResponse { #[cfg(test)] mod test { use super::*; + use serde_json::Value; #[test] #[cfg(feature = "alb")] @@ -121,6 +124,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 7ac22741fc64140c1c0b82a0d2f2ce05933e8d9f Mon Sep 17 00:00:00 2001 From: Jess Izen <44884346+jlizen@users.noreply.github.com> Date: Wed, 27 Aug 2025 10:17:30 -0700 Subject: [PATCH 205/211] chore: prepare lambda_http@0.17.0, lambda_events@0.18.0, lambda_runtime_api_client@0.12.4, lambda_runtime@0.14.4 (#1034) --- lambda-events/Cargo.toml | 2 +- lambda-http/Cargo.toml | 4 ++-- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index cd2d86f2..2cd8c969 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "aws_lambda_events" -version = "0.17.0" +version = "0.18.0" rust-version = "1.81.0" description = "AWS Lambda event definitions" authors = [ diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index b930afdb..4c279e80 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_http" -version = "0.16.0" +version = "0.17.0" authors = [ "David Calavera ", "Harold Sun ", @@ -50,7 +50,7 @@ url = "2.2" [dependencies.aws_lambda_events] path = "../lambda-events" -version = "0.17.0" +version = "0.18.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index dcc2916d..ba8f0fa0 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime_api_client" -version = "0.12.3" +version = "0.12.4" edition = "2021" rust-version = "1.81.0" authors = [ diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 3daefc11..30f0bfa9 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lambda_runtime" -version = "0.14.3" +version = "0.14.4" authors = [ "David Calavera ", "Harold Sun ", From 8e6fac2d58278b5c8aa22a4013ccb9138b908513 Mon Sep 17 00:00:00 2001 From: AntoniaSzecsi <117035062+AntoniaSzecsi@users.noreply.github.com> Date: Thu, 4 Sep 2025 16:35:29 +0100 Subject: [PATCH 206/211] Add local testing infrastructure with AWS Lambda Runtime Interface Emulator (RIE) (#1033) * Add local testing infrastructure with RIE * Update Dockerfile.rie based on PR feedback --------- Co-authored-by: AntoniaSzecsi --- .github/workflows/test-rie.yml | 50 ++++++++++++++++++++++++++++++++++ Dockerfile.rie | 25 +++++++++++++++++ Makefile | 5 +++- README.md | 25 +++++++++++++++++ scripts/test-rie.sh | 22 +++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/test-rie.yml create mode 100644 Dockerfile.rie create mode 100755 scripts/test-rie.sh diff --git a/.github/workflows/test-rie.yml b/.github/workflows/test-rie.yml new file mode 100644 index 00000000..5d777e2d --- /dev/null +++ b/.github/workflows/test-rie.yml @@ -0,0 +1,50 @@ +name: Test with RIE + +on: + pull_request: + branches: [ main ] + push: + branches: [ main ] + +jobs: + test-rie: + runs-on: ubuntu-latest + strategy: + matrix: + example: [basic-lambda, basic-sqs] + + steps: + - uses: actions/checkout@v4 + + - name: Build and test ${{ matrix.example }} with RIE + run: | + docker build -f Dockerfile.rie --build-arg EXAMPLE=${{ matrix.example }} -t rust-lambda-rie-test-${{ matrix.example }} . + + # Start container in background + docker run -d -p 9000:8080 --name rie-test-${{ matrix.example }} rust-lambda-rie-test-${{ matrix.example }} + + # Wait for container to be ready + sleep 5 + + # Test the function based on example type + if [ "${{ matrix.example }}" = "basic-lambda" ]; then + PAYLOAD='{"command": "test from CI"}' + elif [ "${{ matrix.example }}" = "basic-sqs" ]; then + PAYLOAD='{"Records": [{"body": "{\"id\": \"123\", \"text\": \"hello from SQS\"}", "messageId": "test-id", "receiptHandle": "test-handle", "attributes": {}, "messageAttributes": {}, "md5OfBody": "test-md5", "eventSource": "aws:sqs", "eventSourceARN": "arn:aws:sqs:us-east-1:123456789012:test-queue", "awsRegion": "us-east-1"}]}' + fi + + # Make request and verify response + RESPONSE=$(curl -s -XPOST 'http://localhost:9000/2015-03-31/functions/function/invocations' \ + -d "$PAYLOAD" \ + -H 'Content-Type: application/json') + + echo "Response: $RESPONSE" + + # Basic validation that we got a response (not empty) + if [ -z "$RESPONSE" ]; then + echo "Error: Empty response" + exit 1 + fi + + # Stop container + docker stop rie-test-${{ matrix.example }} \ No newline at end of file diff --git a/Dockerfile.rie b/Dockerfile.rie new file mode 100644 index 00000000..1a46b577 --- /dev/null +++ b/Dockerfile.rie @@ -0,0 +1,25 @@ +FROM public.ecr.aws/lambda/provided:al2023 + +RUN dnf install -y gcc +RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y +ENV PATH="/root/.cargo/bin:${PATH}" + +ADD https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/local/bin/aws-lambda-rie +RUN chmod +x /usr/local/bin/aws-lambda-rie + +ARG EXAMPLE=basic-lambda + +COPY Cargo.* /build/ +COPY lambda-runtime /build/lambda-runtime +COPY lambda-runtime-api-client /build/lambda-runtime-api-client +COPY lambda-events /build/lambda-events +COPY lambda-http /build/lambda-http +COPY lambda-extension /build/lambda-extension +COPY examples/${EXAMPLE} /build/examples/${EXAMPLE} + +WORKDIR /build/examples/${EXAMPLE} +RUN cargo build --release +RUN cp target/release/${EXAMPLE} ${LAMBDA_RUNTIME_DIR}/bootstrap + +ENTRYPOINT [] +CMD [ "/usr/local/bin/aws-lambda-rie", "/var/runtime/bootstrap" ] \ No newline at end of file diff --git a/Makefile b/Makefile index ecfd7623..155b7ea1 100644 --- a/Makefile +++ b/Makefile @@ -108,4 +108,7 @@ check-event-features: cargo test --package aws_lambda_events --no-default-features --features streams fmt: - cargo +nightly fmt --all \ No newline at end of file + cargo +nightly fmt --all + +test-rie: + ./scripts/test-rie.sh $(EXAMPLE) \ No newline at end of file diff --git a/README.md b/README.md index 5426c0c9..fdec26a5 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,31 @@ curl -v -X POST \ You can read more about how [cargo lambda watch](https://www.cargo-lambda.info/commands/watch.html) and [cargo lambda invoke](https://www.cargo-lambda.info/commands/invoke.html) work on the project's [documentation page](https://www.cargo-lambda.info). +### Local testing with Runtime Interface Emulator (RIE) + +For testing with the official AWS Lambda Runtime Interface Emulator, use the provided RIE testing infrastructure: + +```bash +make test-rie +``` + +By default, this uses the `basic-lambda` example. To test a different example: + +```bash +make test-rie EXAMPLE=basic-sqs +make test-rie EXAMPLE=http-basic-lambda +``` + +This command will: +1. Build a Docker image with Rust toolchain and RIE +2. Compile the specified example inside the Linux container +3. Start the RIE container on port 9000 +4. Display the appropriate curl command for testing + +Different examples expect different payload formats. Check the example's source code in `examples/EXAMPLE_NAME/src/main.rs` + +This provides automated testing with Docker and RIE, ensuring your Lambda functions work in a Linux environment identical to AWS Lambda. + ### Lambda Debug Proxy Lambdas can be run and debugged locally using a special [Lambda debug proxy](https://github.com/rimutaka/lambda-debug-proxy) (a non-AWS repo maintained by @rimutaka), which is a Lambda function that forwards incoming requests to one AWS SQS queue and reads responses from another queue. A local proxy running on your development computer reads the queue, calls your Lambda locally and sends back the response. This approach allows debugging of Lambda functions locally while being part of your AWS workflow. The Lambda handler code does not need to be modified between the local and AWS versions. diff --git a/scripts/test-rie.sh b/scripts/test-rie.sh new file mode 100755 index 00000000..911cb390 --- /dev/null +++ b/scripts/test-rie.sh @@ -0,0 +1,22 @@ +#!/bin/bash +set -euo pipefail + +EXAMPLE=${1:-basic-lambda} + +echo "Building Docker image with RIE for example: $EXAMPLE..." +docker build -f Dockerfile.rie --build-arg EXAMPLE=$EXAMPLE -t rust-lambda-rie-test . + +echo "Starting RIE container on port 9000..." +docker run -p 9000:8080 rust-lambda-rie-test & +CONTAINER_PID=$! + +echo "Container started. Test with:" +if [ "$EXAMPLE" = "basic-lambda" ]; then + echo "curl -XPOST 'http://localhost:9000/2015-03-31/functions/function/invocations' -d '{\"command\": \"test from RIE\"}' -H 'Content-Type: application/json'" +else + echo "For example '$EXAMPLE', check examples/$EXAMPLE/src/main.rs for the expected payload format." +fi +echo "" +echo "Press Ctrl+C to stop the container." + +wait $CONTAINER_PID \ No newline at end of file From 4c4e5fac53be0a1fdca671b2f7bb1f905247778e Mon Sep 17 00:00:00 2001 From: Ikuma Yamashita Date: Fri, 19 Sep 2025 00:05:31 +0900 Subject: [PATCH 207/211] feat(lambda-events): add Default implementations for all event (#1037) * feat: implement Default trait for all lambda events --- lambda-events/src/encodings/http.rs | 14 ++++----- lambda-events/src/event/appsync/mod.rs | 30 +++++++++++-------- lambda-events/src/event/clientvpn/mod.rs | 4 +-- lambda-events/src/event/cloudformation/mod.rs | 4 +-- .../src/event/cloudformation/provider.rs | 4 +-- lambda-events/src/event/code_commit/mod.rs | 6 ++-- lambda-events/src/event/codebuild/mod.rs | 4 +-- .../src/event/dynamodb/attributes.rs | 20 ++++++------- lambda-events/src/event/dynamodb/mod.rs | 17 +++++++---- lambda-events/src/event/firehose/mod.rs | 8 ++--- lambda-events/src/event/iam/mod.rs | 2 +- lambda-events/src/event/iot/mod.rs | 14 ++++----- lambda-events/src/event/iot_deprecated/mod.rs | 4 +-- .../src/event/lambda_function_urls/mod.rs | 12 ++++---- lambda-events/src/event/lex/mod.rs | 8 ++--- lambda-events/src/event/rabbitmq/mod.rs | 4 +-- lambda-events/src/event/ses/mod.rs | 18 +++++------ lambda-events/src/event/sns/mod.rs | 6 ++-- lambda-events/src/event/streams/mod.rs | 12 ++++---- lambda-http/src/deserializer.rs | 12 ++++---- lambda-runtime/src/runtime.rs | 4 +-- 21 files changed, 109 insertions(+), 98 deletions(-) diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index d978f522..8524f215 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -264,7 +264,7 @@ mod tests { fn from_str() { match Body::from(String::from("foo").as_str()) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -272,7 +272,7 @@ mod tests { fn from_string() { match Body::from(String::from("foo")) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -280,7 +280,7 @@ mod tests { fn from_cow_str() { match Body::from(Cow::from("foo")) { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } @@ -288,7 +288,7 @@ mod tests { fn from_cow_bytes() { match Body::from(Cow::from("foo".as_bytes())) { Body::Binary(_) => (), - not => panic!("expected Body::Binary(...) got {:?}", not), + not => panic!("expected Body::Binary(...) got {not:?}"), } } @@ -296,7 +296,7 @@ mod tests { fn from_bytes() { match Body::from("foo".as_bytes()) { Body::Binary(_) => (), - not => panic!("expected Body::Binary(...) got {:?}", not), + not => panic!("expected Body::Binary(...) got {not:?}"), } } @@ -325,12 +325,12 @@ mod tests { fn serialize_from_maybe_encoded() { match Body::from_maybe_encoded(false, "foo") { Body::Text(_) => (), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } match Body::from_maybe_encoded(true, "Zm9v") { Body::Binary(b) => assert_eq!(&[102, 111, 111], b.as_slice()), - not => panic!("expected Body::Text(...) got {:?}", not), + not => panic!("expected Body::Text(...) got {not:?}"), } } } diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index ed68915e..312843bd 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate where @@ -27,7 +27,7 @@ where } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIamIdentity { #[serde(default)] @@ -55,7 +55,7 @@ pub struct AppSyncIamIdentity { } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncCognitoIdentity where @@ -87,7 +87,7 @@ where pub type AppSyncOperation = String; /// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequest { #[serde(default)] @@ -104,7 +104,7 @@ pub struct AppSyncLambdaAuthorizerRequest { /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered /// this authorization request. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequestContext where @@ -136,7 +136,7 @@ where } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerResponse where @@ -171,7 +171,7 @@ where /// /// See also: /// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncDirectResolverEvent where TArguments: Serialize + DeserializeOwned, @@ -200,7 +200,7 @@ where /// `AppSyncRequest` contains request-related metadata for a resolver invocation, /// including client-sent headers and optional custom domain name. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncRequest { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -219,7 +219,7 @@ pub struct AppSyncRequest { } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncInfo where @@ -243,7 +243,7 @@ where } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncPrevResult where T: Serialize + DeserializeOwned, @@ -270,8 +270,14 @@ pub enum AppSyncIdentity { Lambda(AppSyncIdentityLambda), } +impl Default for AppSyncIdentity { + fn default() -> Self { + AppSyncIdentity::IAM(AppSyncIamIdentity::default()) + } +} + /// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncIdentityOIDC where T: Serialize + DeserializeOwned, @@ -290,7 +296,7 @@ where } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIdentityLambda where diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index e99d7c8c..89712834 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { #[serde(default)] @@ -40,7 +40,7 @@ pub struct ClientVpnConnectionHandlerRequest { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerResponse { pub allow: bool, diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 995ab846..84e793b0 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -50,7 +50,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest where @@ -79,7 +79,7 @@ where pub other: serde_json::Map, } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest where diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index 71277388..dd0043cd 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -39,7 +39,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest where @@ -56,7 +56,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest where diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index e35ab093..021f8942 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -23,7 +23,7 @@ pub struct CodeCommitEvent { pub type CodeCommitEventTime = DateTime; /// `CodeCommitRecord` represents a CodeCommit record -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitRecord { #[serde(default)] @@ -63,7 +63,7 @@ pub struct CodeCommitRecord { } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { pub references: Vec, @@ -80,7 +80,7 @@ pub struct CodeCommitCodeCommit { } /// `CodeCommitReference` represents a Reference object in a CodeCommit object -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitReference { #[serde(default)] diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 4ffb821d..304cf465 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -178,7 +178,7 @@ pub struct CodeBuildEnvironment { } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironmentVariable { /// Name is the name of the environment variable. @@ -239,7 +239,7 @@ pub struct CodeBuildLogs { } /// `CodeBuildPhase` represents the phase of a build and its details -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildPhase where diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs index e1a42c83..ac43adea 100644 --- a/lambda-events/src/event/dynamodb/attributes.rs +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -15,7 +15,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::Null(true) => {} - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -31,7 +31,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::S(ref s) => assert_eq!("value", s.as_str()), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -47,7 +47,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::N(ref n) => assert_eq!("123.45", n.as_str()), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -68,7 +68,7 @@ mod test { .unwrap(); assert_eq!(&expected, b) } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -84,7 +84,7 @@ mod test { let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); match attr { AttributeValue::Bool(b) => assert!(b), - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -103,7 +103,7 @@ mod test { let expected = vec!["Giraffe", "Hippo", "Zebra"]; assert_eq!(expected, s.iter().collect::>()); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -122,7 +122,7 @@ mod test { let expected = vec!["42.2", "-19", "7.5", "3.14"]; assert_eq!(expected, s.iter().collect::>()); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -144,7 +144,7 @@ mod test { .collect::>(); assert_eq!(&expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -167,7 +167,7 @@ mod test { ]; assert_eq!(&expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } let reparsed = serde_json::to_value(attr).unwrap(); @@ -188,7 +188,7 @@ mod test { expected.insert("Age".into(), AttributeValue::N("35".into())); assert_eq!(expected, s); } - other => panic!("unexpected value {:?}", other), + other => panic!("unexpected value {other:?}"), } } } diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 3e1b3ab3..8b8041c0 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -12,12 +12,13 @@ use std::fmt; #[cfg(test)] mod attributes; -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, OldImage, NewAndOldImages, + #[default] KeysOnly, } @@ -33,12 +34,13 @@ impl fmt::Display for StreamViewType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, Enabled, Disabling, + #[default] Disabled, } @@ -54,10 +56,11 @@ impl fmt::Display for StreamStatus { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, + #[default] Latest, AtSequenceNumber, AfterSequenceNumber, @@ -75,9 +78,10 @@ impl fmt::Display for SharedIteratorType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { + #[default] Insert, Modify, Remove, @@ -94,9 +98,10 @@ impl fmt::Display for OperationType { } } -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { + #[default] Hash, Range, } @@ -128,7 +133,7 @@ pub struct Event { /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. -#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { #[serde(rename = "DynamoDBEvent")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index 8bce49ac..a97577f5 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -49,7 +49,7 @@ pub struct KinesisFirehoseEventRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { pub records: Vec, @@ -62,7 +62,7 @@ pub struct KinesisFirehoseResponse { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecord { #[serde(default)] @@ -81,7 +81,7 @@ pub struct KinesisFirehoseResponseRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecordMetadata { #[serde(deserialize_with = "deserialize_lambda_map")] @@ -96,7 +96,7 @@ pub struct KinesisFirehoseResponseRecordMetadata { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseRecordMetadata { #[serde(default)] diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index f4301b1e..42c8a9c7 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -8,7 +8,7 @@ use serde::{ }; /// `IamPolicyDocument` represents an IAM policy document. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { #[serde(default)] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index cb262bd0..f544510f 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -6,7 +6,7 @@ use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { #[serde(default)] @@ -24,7 +24,7 @@ pub struct IoTCoreCustomAuthorizerRequest { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreProtocolData { pub tls: Option, @@ -39,7 +39,7 @@ pub struct IoTCoreProtocolData { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreTlsContext { #[serde(default)] @@ -53,7 +53,7 @@ pub struct IoTCoreTlsContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreHttpContext { #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] @@ -70,7 +70,7 @@ pub struct IoTCoreHttpContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreMqttContext { #[serde(default)] @@ -87,7 +87,7 @@ pub struct IoTCoreMqttContext { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreConnectionMetadata { #[serde(default)] @@ -103,7 +103,7 @@ pub struct IoTCoreConnectionMetadata { /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. /// See -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { pub is_authenticated: bool, diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 30475675..30142606 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -5,7 +5,7 @@ use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerRequest { pub http_context: Option, @@ -33,7 +33,7 @@ pub type IoTTlsContext = IoTCoreTlsContext; /// `IoTCustomAuthorizerResponse` represents the expected format of an IoT device gateway authorization response. /// Deprecated: Use IoTCoreCustomAuthorizerResponse. `IoTCustomAuthorizerResponse` does not correctly model the response schema. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerResponse { pub is_authenticated: bool, diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index a754af0d..646d141a 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequest { /// Version is expected to be `"2.0"` @@ -37,7 +37,7 @@ pub struct LambdaFunctionUrlRequest { } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContext { #[serde(default)] @@ -69,7 +69,7 @@ pub struct LambdaFunctionUrlRequestContext { } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { pub iam: Option, @@ -83,7 +83,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { #[serde(default)] @@ -106,7 +106,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextHttpDescription { #[serde(default)] @@ -129,7 +129,7 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlResponse { pub status_code: i64, diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index 6a458c8a..46258951 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -31,7 +31,7 @@ pub struct LexEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexBot { pub name: Option, @@ -84,7 +84,7 @@ pub struct LexAlternativeIntents { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SlotDetail { pub resolutions: Option>>, @@ -123,7 +123,7 @@ pub type SessionAttributes = HashMap; pub type Slots = HashMap>; -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponse { pub session_attributes: SessionAttributes, @@ -152,7 +152,7 @@ pub struct LexResponseCard { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attachment { pub title: Option, diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index 6c79e2b0..e1a7256b 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -24,7 +24,7 @@ pub struct RabbitMqEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqMessage { pub basic_properties: RabbitMqBasicProperties, @@ -40,7 +40,7 @@ pub struct RabbitMqMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqBasicProperties where diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 9358135d..20498780 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -18,7 +18,7 @@ pub struct SimpleEmailEvent { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailRecord { #[serde(default)] @@ -35,7 +35,7 @@ pub struct SimpleEmailRecord { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailService { pub mail: SimpleEmailMessage, @@ -50,7 +50,7 @@ pub struct SimpleEmailService { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailMessage { pub common_headers: SimpleEmailCommonHeaders, @@ -71,7 +71,7 @@ pub struct SimpleEmailMessage { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceipt { pub recipients: Vec, @@ -94,7 +94,7 @@ pub struct SimpleEmailReceipt { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailHeader { #[serde(default)] @@ -110,7 +110,7 @@ pub struct SimpleEmailHeader { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailCommonHeaders { pub from: Vec, @@ -136,7 +136,7 @@ pub struct SimpleEmailCommonHeaders { /// Types. For example, the FunctionARN and InvocationType fields are only /// present for the Lambda Type, and the BucketName and ObjectKey fields are only /// present for the S3 Type. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceiptAction { #[serde(default)] @@ -160,7 +160,7 @@ pub struct SimpleEmailReceiptAction { pub other: serde_json::Map, } -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailVerdict { #[serde(default)] @@ -177,7 +177,7 @@ pub struct SimpleEmailVerdict { pub type SimpleEmailDispositionValue = String; /// `SimpleEmailDisposition` disposition return for SES to control rule functions -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { pub disposition: SimpleEmailDispositionValue, diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 611a16b7..163c13ac 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -123,7 +123,7 @@ pub struct SnsEventObj { } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsRecordObj { @@ -150,7 +150,7 @@ pub struct SnsRecordObj { /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` #[serde_with::serde_as] -#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] pub struct SnsMessageObj { @@ -412,7 +412,7 @@ mod test { } let parsed: SnsEventObj = serde_json::from_slice(data).unwrap(); - println!("{:?}", parsed); + println!("{parsed:?}"); assert_eq!(parsed.records[0].sns.message.foo, "Hello world!"); assert_eq!(parsed.records[0].sns.message.bar, 123); diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index 673217fc..caf2c02d 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { pub batch_item_failures: Vec, @@ -17,7 +17,7 @@ pub struct KinesisEventResponse { } /// `KinesisBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisBatchItemFailure { #[serde(default)] @@ -32,7 +32,7 @@ pub struct KinesisBatchItemFailure { } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { pub batch_item_failures: Vec, @@ -46,7 +46,7 @@ pub struct DynamoDbEventResponse { } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbBatchItemFailure { #[serde(default)] @@ -61,7 +61,7 @@ pub struct DynamoDbBatchItemFailure { } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { pub batch_item_failures: Vec, @@ -75,7 +75,7 @@ pub struct SqsEventResponse { } /// `SqsBatchItemFailure` is the individual record which failed processing. -#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchItemFailure { #[serde(default)] diff --git a/lambda-http/src/deserializer.rs b/lambda-http/src/deserializer.rs index 4a09ff9a..e0da5e0e 100644 --- a/lambda-http/src/deserializer.rs +++ b/lambda-http/src/deserializer.rs @@ -61,7 +61,7 @@ mod tests { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("12345678912", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -74,7 +74,7 @@ mod tests { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -87,7 +87,7 @@ mod tests { LambdaRequest::ApiGatewayV1(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -100,7 +100,7 @@ mod tests { LambdaRequest::ApiGatewayV2(req) => { assert_eq!("123456789012", req.request_context.account_id.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -118,7 +118,7 @@ mod tests { req.request_context.elb.target_group_arn.unwrap() ); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } @@ -132,7 +132,7 @@ mod tests { LambdaRequest::WebSocket(req) => { assert_eq!("CONNECT", req.request_context.event_type.unwrap()); } - other => panic!("unexpected request variant: {:?}", other), + other => panic!("unexpected request variant: {other:?}"), } } diff --git a/lambda-runtime/src/runtime.rs b/lambda-runtime/src/runtime.rs index 5749fbb7..517ee64f 100644 --- a/lambda-runtime/src/runtime.rs +++ b/lambda-runtime/src/runtime.rs @@ -369,7 +369,7 @@ mod endpoint_tests { }); let next_response = server.mock(|when, then| { when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/response", request_id)) + .path(format!("/2018-06-01/runtime/invocation/{request_id}/response")) .body("{}"); then.status(200).body(""); }); @@ -440,7 +440,7 @@ mod endpoint_tests { let next_response = server.mock(|when, then| { when.method(POST) - .path(format!("/2018-06-01/runtime/invocation/{}/error", request_id)) + .path(format!("/2018-06-01/runtime/invocation/{request_id}/error")) .header("lambda-runtime-function-error-type", "unhandled"); then.status(200).body(""); }); From 726bcea234a965a280801675c16d747d06d3ccf1 Mon Sep 17 00:00:00 2001 From: Chase Naples Date: Wed, 8 Oct 2025 06:33:46 -0400 Subject: [PATCH 208/211] feat: mark selected public enums as #[non_exhaustive] (part of #1016) (#1040) - lambda-events (dynamodb): StreamViewType, StreamStatus, SharedIteratorType, OperationType, KeyType - lambda-http: PayloadError, JsonPayloadError, FormUrlEncodedPayloadError These enums are likely to grow; marking them non_exhaustive helps avoid semver hazards for downstream consumers who pattern-match exhaustively. --- lambda-events/src/event/dynamodb/mod.rs | 5 +++++ lambda-http/src/ext/request.rs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 8b8041c0..316768dc 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -13,6 +13,7 @@ use std::fmt; mod attributes; #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, @@ -35,6 +36,7 @@ impl fmt::Display for StreamViewType { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, @@ -57,6 +59,7 @@ impl fmt::Display for StreamStatus { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, @@ -79,6 +82,7 @@ impl fmt::Display for SharedIteratorType { } #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { #[default] @@ -99,6 +103,7 @@ impl fmt::Display for OperationType { } #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[non_exhaustive] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { #[default] diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index c56518f6..dc14532e 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -13,6 +13,7 @@ use crate::Body; /// /// Returned by [`RequestPayloadExt::payload()`] #[derive(Debug)] +#[non_exhaustive] pub enum PayloadError { /// Returned when `application/json` bodies fail to deserialize a payload Json(serde_json::Error), @@ -22,6 +23,7 @@ pub enum PayloadError { /// Indicates a problem processing a JSON payload. #[derive(Debug)] +#[non_exhaustive] pub enum JsonPayloadError { /// Problem deserializing a JSON payload. Parsing(serde_json::Error), @@ -29,6 +31,7 @@ pub enum JsonPayloadError { /// Indicates a problem processing an x-www-form-urlencoded payload. #[derive(Debug)] +#[non_exhaustive] pub enum FormUrlEncodedPayloadError { /// Problem deserializing an x-www-form-urlencoded payload. Parsing(SerdeError), From 3c8a8be5649e22fed475fa0215271e5d85c74425 Mon Sep 17 00:00:00 2001 From: Astraea Quinn S <52372765+PartiallyUntyped@users.noreply.github.com> Date: Wed, 8 Oct 2025 15:20:08 +0200 Subject: [PATCH 209/211] Bump rustc version to 1.82.0 (#1044) * Update Dependencies Signed-off-by: Astraea Quinn Sinclair --- .github/workflows/build-events.yml | 2 +- .github/workflows/build-extension.yml | 2 +- .github/workflows/build-integration-test.yml | 2 +- .github/workflows/build-runtime.yml | 2 +- Cargo.toml | 2 ++ README.md | 2 +- lambda-events/Cargo.toml | 2 +- lambda-extension/Cargo.toml | 2 +- lambda-http/Cargo.toml | 6 +++--- lambda-integration-tests/Cargo.toml | 2 +- lambda-runtime-api-client/Cargo.toml | 2 +- lambda-runtime/Cargo.toml | 8 ++++---- 12 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml index 372375b5..624f96d6 100644 --- a/.github/workflows/build-events.yml +++ b/.github/workflows/build-events.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml index 2daaa40d..f823dbb4 100644 --- a/.github/workflows/build-extension.yml +++ b/.github/workflows/build-extension.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-integration-test.yml b/.github/workflows/build-integration-test.yml index c0d43e25..dd9bd68f 100644 --- a/.github/workflows/build-integration-test.yml +++ b/.github/workflows/build-integration-test.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml index c5cf2d08..0327bd34 100644 --- a/.github/workflows/build-runtime.yml +++ b/.github/workflows/build-runtime.yml @@ -21,7 +21,7 @@ jobs: strategy: matrix: toolchain: - - "1.81.0" # Current MSRV + - "1.82.0" # Current MSRV - stable env: RUST_BACKTRACE: 1 diff --git a/Cargo.toml b/Cargo.toml index 867e9c0d..7baf97c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,3 +32,5 @@ pin-project-lite = "0.2" tower = "0.5" tower-layer = "0.3" tower-service = "0.3" + + diff --git a/README.md b/README.md index fdec26a5..954b195c 100644 --- a/README.md +++ b/README.md @@ -485,7 +485,7 @@ fn main() -> Result<(), Box> { ## Supported Rust Versions (MSRV) -The AWS Lambda Rust Runtime requires a minimum of Rust 1.81.0, and is not guaranteed to build on compiler versions earlier than that. +The AWS Lambda Rust Runtime requires a minimum of Rust 1.82.0, and is not guaranteed to build on compiler versions earlier than that. ## Security diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml index 2cd8c969..c21e959b 100644 --- a/lambda-events/Cargo.toml +++ b/lambda-events/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "aws_lambda_events" version = "0.18.0" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "AWS Lambda event definitions" authors = [ "Christian Legnitto ", diff --git a/lambda-extension/Cargo.toml b/lambda-extension/Cargo.toml index 515a97fe..427b744a 100644 --- a/lambda-extension/Cargo.toml +++ b/lambda-extension/Cargo.toml @@ -2,7 +2,7 @@ name = "lambda-extension" version = "0.12.2" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 4c279e80..f5f3dfe1 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -6,7 +6,7 @@ authors = [ "Harold Sun ", ] edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "Application Load Balancer and API Gateway event types for AWS Lambda" keywords = ["AWS", "Lambda", "APIGateway", "ALB", "API"] license = "Apache-2.0" @@ -55,8 +55,8 @@ default-features = false features = ["alb", "apigw"] [dev-dependencies] -axum-core = "0.5.0" -axum-extra = { version = "0.10.0", features = ["query"] } +axum-core = "0.5.4" +axum-extra = { version = "0.10.2", features = ["query"] } lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client" } log = "^0.4" maplit = "1.0" diff --git a/lambda-integration-tests/Cargo.toml b/lambda-integration-tests/Cargo.toml index 85d5fca6..2e9817c2 100644 --- a/lambda-integration-tests/Cargo.toml +++ b/lambda-integration-tests/Cargo.toml @@ -3,7 +3,7 @@ name = "lambda-integration-tests" version = "0.1.0" authors = ["Maxime David"] edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" description = "AWS Lambda Runtime integration tests" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" diff --git a/lambda-runtime-api-client/Cargo.toml b/lambda-runtime-api-client/Cargo.toml index ba8f0fa0..39311a8c 100644 --- a/lambda-runtime-api-client/Cargo.toml +++ b/lambda-runtime-api-client/Cargo.toml @@ -2,7 +2,7 @@ name = "lambda_runtime_api_client" version = "0.12.4" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" authors = [ "David Calavera ", "Harold Sun ", diff --git a/lambda-runtime/Cargo.toml b/lambda-runtime/Cargo.toml index 30f0bfa9..f8192e5c 100644 --- a/lambda-runtime/Cargo.toml +++ b/lambda-runtime/Cargo.toml @@ -7,7 +7,7 @@ authors = [ ] description = "AWS Lambda Runtime" edition = "2021" -rust-version = "1.81.0" +rust-version = "1.82.0" license = "Apache-2.0" repository = "https://github.com/awslabs/aws-lambda-rust-runtime" categories = ["web-programming::http-server"] @@ -40,7 +40,7 @@ hyper = { workspace = true, features = ["http1", "client"] } lambda-extension = { version = "0.12.2", path = "../lambda-extension", default-features = false, optional = true } lambda_runtime_api_client = { version = "0.12.3", path = "../lambda-runtime-api-client", default-features = false } miette = { version = "7.2.0", optional = true } -opentelemetry-semantic-conventions = { version = "0.29", optional = true, features = ["semconv_experimental"] } +opentelemetry-semantic-conventions = { version = "0.31", optional = true, features = ["semconv_experimental"] } pin-project = "1" serde = { version = "1", features = ["derive", "rc"] } serde_json = "^1" @@ -56,7 +56,7 @@ tower = { workspace = true, features = ["util"] } tracing = { version = "0.1", features = ["log"] } [dev-dependencies] -httpmock = "0.7.0" +httpmock = "0.8.1" hyper-util = { workspace = true, features = [ "client", "client-legacy", @@ -74,4 +74,4 @@ pin-project-lite = { workspace = true } tracing-appender = "0.2" [package.metadata.docs.rs] -all-features = true \ No newline at end of file +all-features = true From b40c011950489aa675a00ffdd863e8bcddbc3489 Mon Sep 17 00:00:00 2001 From: mpindaru <102007013+mpindaru@users.noreply.github.com> Date: Wed, 8 Oct 2025 16:11:54 +0100 Subject: [PATCH 210/211] feat(lambda-events): mark public structs/enums as #[non_exhaustive] (#1045) * feat(lambda-events): mark public structs/enums as #[non_exhaustive] * fix examples --------- Co-authored-by: Mara Pindaru --- .../src/main.rs | 18 +++-- .../src/main.rs | 42 +++++----- examples/basic-s3-thumbnail/src/main.rs | 81 ++++++++++--------- lambda-events/src/encodings/http.rs | 1 + lambda-events/src/encodings/mod.rs | 1 + lambda-events/src/encodings/time.rs | 4 + lambda-events/src/event/activemq/mod.rs | 3 + lambda-events/src/event/alb/mod.rs | 3 + lambda-events/src/event/apigw/mod.rs | 27 +++++++ lambda-events/src/event/appsync/mod.rs | 13 +++ lambda-events/src/event/autoscaling/mod.rs | 1 + .../src/event/bedrock_agent_runtime/mod.rs | 6 ++ lambda-events/src/event/chime_bot/mod.rs | 4 + lambda-events/src/event/clientvpn/mod.rs | 2 + lambda-events/src/event/cloudformation/mod.rs | 6 ++ .../src/event/cloudformation/provider.rs | 6 ++ .../src/event/cloudwatch_alarms/mod.rs | 15 ++++ .../src/event/cloudwatch_events/cloudtrail.rs | 7 ++ .../src/event/cloudwatch_events/codedeploy.rs | 3 + .../event/cloudwatch_events/codepipeline.rs | 4 + .../src/event/cloudwatch_events/ec2.rs | 1 + .../src/event/cloudwatch_events/emr.rs | 4 + .../src/event/cloudwatch_events/gamelift.rs | 12 +++ .../src/event/cloudwatch_events/glue.rs | 8 ++ .../src/event/cloudwatch_events/health.rs | 3 + .../src/event/cloudwatch_events/kms.rs | 1 + .../src/event/cloudwatch_events/macie.rs | 18 +++++ .../src/event/cloudwatch_events/mod.rs | 1 + .../src/event/cloudwatch_events/opsworks.rs | 4 + .../src/event/cloudwatch_events/signin.rs | 4 + .../src/event/cloudwatch_events/sms.rs | 1 + .../src/event/cloudwatch_events/ssm.rs | 14 ++++ .../src/event/cloudwatch_events/tag.rs | 1 + .../event/cloudwatch_events/trustedadvisor.rs | 1 + .../src/event/cloudwatch_logs/mod.rs | 4 + lambda-events/src/event/code_commit/mod.rs | 4 + lambda-events/src/event/codebuild/mod.rs | 9 +++ lambda-events/src/event/codedeploy/mod.rs | 3 + .../src/event/codepipeline_cloudwatch/mod.rs | 3 + .../src/event/codepipeline_job/mod.rs | 11 +++ lambda-events/src/event/cognito/mod.rs | 53 ++++++++++++ lambda-events/src/event/config/mod.rs | 1 + lambda-events/src/event/connect/mod.rs | 5 ++ .../event/documentdb/events/commom_types.rs | 6 ++ .../event/documentdb/events/delete_event.rs | 1 + .../documentdb/events/drop_database_event.rs | 1 + .../src/event/documentdb/events/drop_event.rs | 1 + .../event/documentdb/events/insert_event.rs | 1 + .../documentdb/events/invalidate_event.rs | 1 + .../event/documentdb/events/rename_event.rs | 1 + .../event/documentdb/events/replace_event.rs | 1 + .../event/documentdb/events/update_event.rs | 3 + lambda-events/src/event/documentdb/mod.rs | 3 + lambda-events/src/event/dynamodb/mod.rs | 16 ++-- lambda-events/src/event/ecr_scan/mod.rs | 3 + lambda-events/src/event/eventbridge/mod.rs | 1 + lambda-events/src/event/firehose/mod.rs | 6 ++ lambda-events/src/event/iam/mod.rs | 2 + lambda-events/src/event/iot/mod.rs | 7 ++ lambda-events/src/event/iot_1_click/mod.rs | 5 ++ lambda-events/src/event/iot_button/mod.rs | 1 + lambda-events/src/event/iot_deprecated/mod.rs | 2 + lambda-events/src/event/kafka/mod.rs | 2 + lambda-events/src/event/kinesis/analytics.rs | 4 + lambda-events/src/event/kinesis/event.rs | 6 ++ .../src/event/lambda_function_urls/mod.rs | 6 ++ lambda-events/src/event/lex/mod.rs | 9 +++ lambda-events/src/event/rabbitmq/mod.rs | 3 + lambda-events/src/event/s3/batch_job.rs | 5 ++ lambda-events/src/event/s3/event.rs | 7 ++ lambda-events/src/event/s3/object_lambda.rs | 10 +++ lambda-events/src/event/secretsmanager/mod.rs | 1 + lambda-events/src/event/ses/mod.rs | 10 +++ lambda-events/src/event/sns/mod.rs | 13 +++ lambda-events/src/event/sqs/mod.rs | 11 +++ lambda-events/src/event/streams/mod.rs | 6 ++ lambda-events/src/time_window.rs | 3 + lambda-http/src/response.rs | 3 +- 78 files changed, 507 insertions(+), 67 deletions(-) diff --git a/examples/advanced-sqs-partial-batch-failures/src/main.rs b/examples/advanced-sqs-partial-batch-failures/src/main.rs index 6cea2f93..4af67b8c 100644 --- a/examples/advanced-sqs-partial-batch-failures/src/main.rs +++ b/examples/advanced-sqs-partial-batch-failures/src/main.rs @@ -96,11 +96,17 @@ where } }, ) - .map(|id| BatchItemFailure { item_identifier: id }) + .map(|id| { + let mut failure_item = BatchItemFailure::default(); + failure_item.item_identifier = id; + failure_item + }) .collect(); - Ok(SqsBatchResponse { - batch_item_failures: failure_items, + Ok({ + let mut response = SqsBatchResponse::default(); + response.batch_item_failures = failure_items; + response }) } @@ -140,8 +146,10 @@ mod test { .unwrap(); let lambda_event = LambdaEvent { - payload: SqsEventObj { - records: vec![msg_to_fail, msg_to_succeed], + payload: { + let mut event_object = SqsEventObj::default(); + event_object.records = vec![msg_to_fail, msg_to_succeed]; + event_object }, context: Context::default(), }; diff --git a/examples/basic-s3-object-lambda-thumbnail/src/main.rs b/examples/basic-s3-object-lambda-thumbnail/src/main.rs index 699fb044..2f844059 100644 --- a/examples/basic-s3-object-lambda-thumbnail/src/main.rs +++ b/examples/basic-s3-object-lambda-thumbnail/src/main.rs @@ -126,24 +126,28 @@ mod tests { } fn get_s3_event() -> S3ObjectLambdaEvent { - S3ObjectLambdaEvent { - x_amz_request_id: ("ID".to_string()), - head_object_context: (Some(HeadObjectContext::default())), - list_objects_context: (Some(ListObjectsContext::default())), - get_object_context: (Some(GetObjectContext { - input_s3_url: ("S3_URL".to_string()), - output_route: ("O_ROUTE".to_string()), - output_token: ("O_TOKEN".to_string()), - })), - list_objects_v2_context: (Some(ListObjectsV2Context::default())), - protocol_version: ("VERSION".to_string()), - user_identity: (UserIdentity::default()), - user_request: (UserRequest::default()), - configuration: (Configuration { - access_point_arn: ("APRN".to_string()), - supporting_access_point_arn: ("SAPRN".to_string()), - payload: (json!(null)), - }), - } + let mut event = S3ObjectLambdaEvent::default(); + event.x_amz_request_id = "ID".to_string(); + event.head_object_context = Some(HeadObjectContext::default()); + event.list_objects_context = Some(ListObjectsContext::default()); + event.get_object_context = Some({ + let mut context = GetObjectContext::default(); + context.input_s3_url = "S3_URL".to_string(); + context.output_route = "O_ROUTE".to_string(); + context.output_token = "O_TOKEN".to_string(); + context + }); + event.list_objects_v2_context = Some(ListObjectsV2Context::default()); + event.protocol_version = "VERSION".to_string(); + event.user_identity = UserIdentity::default(); + event.user_request = UserRequest::default(); + event.configuration = { + let mut configuration = Configuration::default(); + configuration.access_point_arn = "APRN".to_string(); + configuration.supporting_access_point_arn = "SAPRN".to_string(); + configuration.payload = json!(null); + configuration + }; + event } } diff --git a/examples/basic-s3-thumbnail/src/main.rs b/examples/basic-s3-thumbnail/src/main.rs index d09da116..0d647b05 100644 --- a/examples/basic-s3-thumbnail/src/main.rs +++ b/examples/basic-s3-thumbnail/src/main.rs @@ -185,46 +185,53 @@ mod tests { } fn get_s3_event(event_name: &str, bucket_name: &str, object_key: &str) -> S3Event { - S3Event { - records: (vec![get_s3_event_record(event_name, bucket_name, object_key)]), - } + let mut event = S3Event::default(); + event.records = vec![get_s3_event_record(event_name, bucket_name, object_key)]; + event } fn get_s3_event_record(event_name: &str, bucket_name: &str, object_key: &str) -> S3EventRecord { - let s3_entity = S3Entity { - schema_version: (Some(String::default())), - configuration_id: (Some(String::default())), - bucket: (S3Bucket { - name: (Some(bucket_name.to_string())), - owner_identity: Some(S3UserIdentity { - principal_id: (Some(String::default())), - }), - arn: (Some(String::default())), - }), - object: (S3Object { - key: (Some(object_key.to_string())), - size: (Some(1)), - url_decoded_key: (Some(String::default())), - version_id: (Some(String::default())), - e_tag: (Some(String::default())), - sequencer: (Some(String::default())), - }), + let mut s3_bucket = S3Bucket::default(); + s3_bucket.name = (Some(bucket_name.to_string())); + s3_bucket.owner_identity = { + let mut s3_user_identity = S3UserIdentity::default(); + s3_user_identity.principal_id = Some(String::default()); + Some(s3_user_identity) }; - - S3EventRecord { - event_version: (Some(String::default())), - event_source: (Some(String::default())), - aws_region: (Some(String::default())), - event_time: (chrono::DateTime::default()), - event_name: (Some(event_name.to_string())), - principal_id: (S3UserIdentity { - principal_id: (Some("X".to_string())), - }), - request_parameters: (S3RequestParameters { - source_ip_address: (Some(String::default())), - }), - response_elements: (HashMap::new()), - s3: (s3_entity), - } + s3_bucket.arn = Some(String::default()); + + let mut s3_object = S3Object::default(); + s3_object.key = Some(object_key.to_string()); + s3_object.size = Some(1); + s3_object.url_decoded_key = Some(String::default()); + s3_object.version_id = Some(String::default()); + s3_object.e_tag = Some(String::default()); + s3_object.sequencer = Some(String::default()); + + let mut s3_entity = S3Entity::default(); + s3_entity.schema_version = Some(String::default()); + s3_entity.configuration_id = Some(String::default()); + s3_entity.bucket = s3_bucket; + s3_entity.object = s3_object; + + let mut s3_event_record = S3EventRecord::default(); + s3_event_record.event_version = Some(String::default()); + s3_event_record.event_source = Some(String::default()); + s3_event_record.aws_region = Some(String::default()); + s3_event_record.event_time = chrono::DateTime::default(); + s3_event_record.event_name = Some(event_name.to_string()); + s3_event_record.principal_id = { + let mut s3_user_identity = S3UserIdentity::default(); + s3_user_identity.principal_id = Some("X".to_string()); + s3_user_identity + }; + s3_event_record.request_parameters = { + let mut s3_request_parameters = S3RequestParameters::default(); + s3_request_parameters.source_ip_address = Some(String::default()); + s3_request_parameters + }; + s3_event_record.response_elements = HashMap::new(); + s3_event_record.s3 = s3_entity; + s3_event_record } } diff --git a/lambda-events/src/encodings/http.rs b/lambda-events/src/encodings/http.rs index 8524f215..32137a75 100644 --- a/lambda-events/src/encodings/http.rs +++ b/lambda-events/src/encodings/http.rs @@ -60,6 +60,7 @@ use std::{borrow::Cow, mem::take, ops::Deref, pin::Pin, task::Poll}; /// /// For more information about API Gateway's body types, /// refer to [this documentation](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-payload-encodings.html). +#[non_exhaustive] #[derive(Debug, Default, Eq, PartialEq)] pub enum Body { /// An empty body diff --git a/lambda-events/src/encodings/mod.rs b/lambda-events/src/encodings/mod.rs index f7520c30..8b308947 100644 --- a/lambda-events/src/encodings/mod.rs +++ b/lambda-events/src/encodings/mod.rs @@ -17,6 +17,7 @@ pub use self::http::*; pub type Error = Box; /// Binary data encoded in base64. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Base64Data( #[serde(deserialize_with = "deserialize_base64")] diff --git a/lambda-events/src/encodings/time.rs b/lambda-events/src/encodings/time.rs index c7ca04a6..4e6d802b 100644 --- a/lambda-events/src/encodings/time.rs +++ b/lambda-events/src/encodings/time.rs @@ -7,6 +7,7 @@ use serde::{ use std::ops::{Deref, DerefMut}; /// Timestamp with millisecond precision. +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MillisecondTimestamp( #[serde(deserialize_with = "deserialize_milliseconds")] @@ -29,6 +30,7 @@ impl DerefMut for MillisecondTimestamp { } /// Timestamp with second precision. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondTimestamp( #[serde(deserialize_with = "deserialize_seconds")] @@ -51,6 +53,7 @@ impl DerefMut for SecondTimestamp { } /// Duration with second precision. +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct SecondDuration( #[serde(deserialize_with = "deserialize_duration_seconds")] @@ -73,6 +76,7 @@ impl DerefMut for SecondDuration { } /// Duration with minute precision. +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct MinuteDuration( #[serde(deserialize_with = "deserialize_duration_minutes")] diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs index 4bced0ab..60ef8568 100644 --- a/lambda-events/src/event/activemq/mod.rs +++ b/lambda-events/src/event/activemq/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqEvent { @@ -22,6 +23,7 @@ pub struct ActiveMqEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqMessage { @@ -59,6 +61,7 @@ pub struct ActiveMqMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActiveMqDestination { diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 5ec4d242..2e89f588 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -12,6 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequest { @@ -44,6 +45,7 @@ pub struct AlbTargetGroupRequest { } /// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupRequestContext { @@ -58,6 +60,7 @@ pub struct AlbTargetGroupRequestContext { } /// `ElbContext` contains the information to identify the ARN invoking the lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ElbContext { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index c7a6175b..199447ec 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -13,6 +13,7 @@ use serde_json::Value; use std::collections::HashMap; /// `ApiGatewayProxyRequest` contains data coming from the API Gateway proxy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequest { @@ -82,6 +83,7 @@ pub struct ApiGatewayProxyResponse { /// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the /// Lambda function. It also includes Cognito identity information for the caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyRequestContext { @@ -134,6 +136,7 @@ pub struct ApiGatewayProxyRequestContext { } /// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequest { @@ -191,6 +194,7 @@ pub struct ApiGatewayV2httpRequest { } /// `ApiGatewayV2httpRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContext { @@ -230,6 +234,7 @@ pub struct ApiGatewayV2httpRequestContext { } /// `ApiGatewayRequestAuthorizer` contains authorizer information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct ApiGatewayRequestAuthorizer { #[serde(skip_serializing_if = "Option::is_none")] @@ -254,6 +259,7 @@ pub struct ApiGatewayRequestAuthorizer { } /// `ApiGatewayRequestAuthorizerJwtDescription` contains JWT authorizer information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerJwtDescription { @@ -272,6 +278,7 @@ pub struct ApiGatewayRequestAuthorizerJwtDescription { } /// `ApiGatewayRequestAuthorizerIamDescription` contains IAM information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerIamDescription { @@ -299,6 +306,7 @@ pub struct ApiGatewayRequestAuthorizerIamDescription { } /// `ApiGatewayRequestAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestAuthorizerCognitoIdentity { @@ -317,6 +325,7 @@ pub struct ApiGatewayRequestAuthorizerCognitoIdentity { } /// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextHttpDescription { @@ -365,6 +374,7 @@ pub struct ApiGatewayV2httpResponse { } /// `ApiGatewayRequestIdentity` contains identity information for the request caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayRequestIdentity { @@ -405,6 +415,7 @@ pub struct ApiGatewayRequestIdentity { } /// `ApiGatewayWebsocketProxyRequest` contains data coming from the API Gateway proxy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequest { @@ -453,6 +464,7 @@ pub struct ApiGatewayWebsocketProxyRequest { /// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify /// the AWS account and resources invoking the Lambda function. It also includes /// Cognito identity information for the caller. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayWebsocketProxyRequestContext { @@ -522,6 +534,7 @@ pub struct ApiGatewayWebsocketProxyRequestContext { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { @@ -543,6 +556,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { @@ -567,6 +581,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity { @@ -584,6 +599,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidit } /// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthentication { @@ -599,6 +615,7 @@ pub struct ApiGatewayV2httpRequestContextAuthentication { } /// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { @@ -623,6 +640,7 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { } /// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { @@ -639,6 +657,7 @@ pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { @@ -669,6 +688,7 @@ pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV1Request { @@ -711,6 +731,7 @@ pub struct ApiGatewayV2CustomAuthorizerV1Request { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerV2Request { @@ -755,6 +776,7 @@ pub struct ApiGatewayV2CustomAuthorizerV2Request { /// `ApiGatewayCustomAuthorizerContext` represents the expected format of an API Gateway custom authorizer response. /// Deprecated. Code should be updated to use the Authorizer map from APIGatewayRequestIdentity. Ex: Authorizer["principalId"] +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerContext { @@ -773,6 +795,7 @@ pub struct ApiGatewayCustomAuthorizerContext { } /// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { @@ -808,6 +831,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { } /// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequest { @@ -828,6 +852,7 @@ pub struct ApiGatewayCustomAuthorizerRequest { } /// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { @@ -895,6 +920,7 @@ where } /// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerSimpleResponse @@ -914,6 +940,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2CustomAuthorizerIamPolicyResponse diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs index 312843bd..223c706b 100644 --- a/lambda-events/src/event/appsync/mod.rs +++ b/lambda-events/src/event/appsync/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// Deprecated: `AppSyncResolverTemplate` does not represent resolver events sent by AppSync. Instead directly model your input schema, or use `map[string]string`, `json.RawMessage`,` interface{}`, etc.. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncResolverTemplate @@ -27,6 +28,7 @@ where } /// `AppSyncIamIdentity` contains information about the caller authed via IAM. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIamIdentity { @@ -55,6 +57,7 @@ pub struct AppSyncIamIdentity { } /// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncCognitoIdentity @@ -87,6 +90,7 @@ where pub type AppSyncOperation = String; /// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequest { @@ -104,6 +108,7 @@ pub struct AppSyncLambdaAuthorizerRequest { /// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered /// this authorization request. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerRequestContext @@ -136,6 +141,7 @@ where } /// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncLambdaAuthorizerResponse @@ -171,6 +177,7 @@ where /// /// See also: /// - [AppSync resolver mapping template context reference](https://docs.aws.amazon.com/appsync/latest/devguide/resolver-context-reference.html) +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncDirectResolverEvent where @@ -200,6 +207,7 @@ where /// `AppSyncRequest` contains request-related metadata for a resolver invocation, /// including client-sent headers and optional custom domain name. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncRequest { @@ -219,6 +227,7 @@ pub struct AppSyncRequest { } /// `AppSyncInfo` contains metadata about the current GraphQL field being resolved. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncInfo @@ -243,6 +252,7 @@ where } /// `AppSyncPrevResult` contains the result of the previous step in a pipeline resolver. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncPrevResult where @@ -261,6 +271,7 @@ where /// `AppSyncIdentity` represents the identity of the caller as determined by the /// configured AppSync authorization mechanism (IAM, Cognito, OIDC, or Lambda). +#[non_exhaustive] #[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(untagged, rename_all = "camelCase")] pub enum AppSyncIdentity { @@ -277,6 +288,7 @@ impl Default for AppSyncIdentity { } /// `AppSyncIdentityOIDC` represents identity information when using OIDC-based authorization. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] pub struct AppSyncIdentityOIDC where @@ -296,6 +308,7 @@ where } /// `AppSyncIdentityLambda` represents identity information when using AWS Lambda +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AppSyncIdentityLambda diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs index cbcde746..9a0eda8a 100644 --- a/lambda-events/src/event/autoscaling/mod.rs +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingEvent diff --git a/lambda-events/src/event/bedrock_agent_runtime/mod.rs b/lambda-events/src/event/bedrock_agent_runtime/mod.rs index e7b4e69c..ce119914 100644 --- a/lambda-events/src/event/bedrock_agent_runtime/mod.rs +++ b/lambda-events/src/event/bedrock_agent_runtime/mod.rs @@ -4,6 +4,7 @@ use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from Agents for Amazon Bedrock. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AgentEvent { @@ -40,6 +41,7 @@ pub struct AgentEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct RequestBody { @@ -54,6 +56,7 @@ pub struct RequestBody { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Content { @@ -68,6 +71,7 @@ pub struct Content { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Property { @@ -86,6 +90,7 @@ pub struct Property { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Parameter { @@ -104,6 +109,7 @@ pub struct Parameter { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Agent { diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs index 42b9ef0e..7a0990ef 100644 --- a/lambda-events/src/event/chime_bot/mod.rs +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEvent { @@ -28,6 +29,7 @@ pub struct ChimeBotEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventSender { @@ -46,6 +48,7 @@ pub struct ChimeBotEventSender { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventDiscussion { @@ -64,6 +67,7 @@ pub struct ChimeBotEventDiscussion { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChimeBotEventInboundHttpsEndpoint { diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs index 89712834..3d6152c9 100644 --- a/lambda-events/src/event/clientvpn/mod.rs +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerRequest { @@ -40,6 +41,7 @@ pub struct ClientVpnConnectionHandlerRequest { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientVpnConnectionHandlerResponse { diff --git a/lambda-events/src/event/cloudformation/mod.rs b/lambda-events/src/event/cloudformation/mod.rs index 84e793b0..3ea66e30 100644 --- a/lambda-events/src/event/cloudformation/mod.rs +++ b/lambda-events/src/event/cloudformation/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; pub mod provider; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest @@ -25,6 +26,7 @@ impl Default for CloudFormationCustomResourceRequest { } } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest @@ -50,6 +52,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest @@ -79,6 +82,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest @@ -105,6 +109,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse { @@ -125,6 +130,7 @@ pub struct CloudFormationCustomResourceResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudFormationCustomResourceResponseStatus { diff --git a/lambda-events/src/event/cloudformation/provider.rs b/lambda-events/src/event/cloudformation/provider.rs index dd0043cd..34e8136f 100644 --- a/lambda-events/src/event/cloudformation/provider.rs +++ b/lambda-events/src/event/cloudformation/provider.rs @@ -7,6 +7,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(tag = "RequestType")] pub enum CloudFormationCustomResourceRequest @@ -28,6 +29,7 @@ impl Default for CloudFormationCustomResourceRequest { } } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CreateRequest @@ -39,6 +41,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct UpdateRequest @@ -56,6 +59,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct DeleteRequest @@ -69,6 +73,7 @@ where // No `other` catch-all here; any additional fields will be caught in `common.other` instead } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CommonRequestParams @@ -90,6 +95,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudFormationCustomResourceResponse diff --git a/lambda-events/src/event/cloudwatch_alarms/mod.rs b/lambda-events/src/event/cloudwatch_alarms/mod.rs index c3efebe3..46c9503a 100644 --- a/lambda-events/src/event/cloudwatch_alarms/mod.rs +++ b/lambda-events/src/event/cloudwatch_alarms/mod.rs @@ -12,6 +12,7 @@ use serde_json::Value; /// You probably want to use `CloudWatchMetricAlarm` or `CloudWatchCompositeAlarm` if you know which kind of alarm your function is receiving. /// For examples of events that come via CloudWatch Alarms, /// see +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarm @@ -50,6 +51,7 @@ pub type CloudWatchMetricAlarm = pub type CloudWatchCompositeAlarm = CloudWatchAlarm; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmData @@ -75,6 +77,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmState @@ -99,6 +102,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricAlarmConfiguration { @@ -115,6 +119,7 @@ pub struct CloudWatchMetricAlarmConfiguration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricDefinition { @@ -131,6 +136,7 @@ pub struct CloudWatchMetricDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricStatDefinition { @@ -149,6 +155,7 @@ pub struct CloudWatchMetricStatDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchMetricStatMetricDefinition { @@ -165,6 +172,7 @@ pub struct CloudWatchMetricStatMetricDefinition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchCompositeAlarmConfiguration { @@ -181,6 +189,7 @@ pub struct CloudWatchCompositeAlarmConfiguration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum CloudWatchAlarmStateValue { @@ -190,6 +199,7 @@ pub enum CloudWatchAlarmStateValue { InsufficientData, } +#[non_exhaustive] #[derive(Clone, Debug, PartialEq)] pub enum CloudWatchAlarmStateReasonData { Metric(CloudWatchAlarmStateReasonDataMetric), @@ -203,6 +213,7 @@ impl Default for CloudWatchAlarmStateReasonData { } } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateReasonDataMetric { @@ -234,6 +245,7 @@ pub struct CloudWatchAlarmStateReasonDataMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateEvaluatedDatapoint { @@ -253,6 +265,7 @@ pub struct CloudWatchAlarmStateEvaluatedDatapoint { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClodWatchAlarmStateReasonDataComposite { @@ -267,6 +280,7 @@ pub struct ClodWatchAlarmStateReasonDataComposite { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateTriggeringAlarm { @@ -281,6 +295,7 @@ pub struct CloudWatchAlarmStateTriggeringAlarm { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchAlarmStateTriggeringAlarmState { diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs index 08b7500a..70275c4f 100644 --- a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -2,6 +2,7 @@ use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AWSAPICall { @@ -32,6 +33,7 @@ pub struct AWSAPICall { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionIssuer { @@ -49,6 +51,7 @@ pub struct SessionIssuer { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct WebIdFederationData { @@ -63,6 +66,7 @@ pub struct WebIdFederationData { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attributes { @@ -77,6 +81,7 @@ pub struct Attributes { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionContext { @@ -94,6 +99,7 @@ pub struct SessionContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OnBehalfOf { @@ -109,6 +115,7 @@ pub struct OnBehalfOf { } // https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs index b52528ca..f01a5bba 100644 --- a/lambda-events/src/event/cloudwatch_events/codedeploy.rs +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StateChangeNotification { @@ -20,6 +21,7 @@ pub struct StateChangeNotification { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeploymentStateChangeNotification { @@ -39,6 +41,7 @@ pub struct DeploymentStateChangeNotification { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChangeNotification { diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs index e99798e7..89f2029c 100644 --- a/lambda-events/src/event/cloudwatch_events/codepipeline.rs +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PipelineExecutionStateChange { @@ -19,6 +20,7 @@ pub struct PipelineExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StageExecutionStateChange { @@ -37,6 +39,7 @@ pub struct StageExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionExecutionStateChange { @@ -59,6 +62,7 @@ pub struct ActionExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionExecutionStateChangeType { diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs index c77fab4f..378b64f1 100644 --- a/lambda-events/src/event/cloudwatch_events/ec2.rs +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs index 4aed4818..f30d5334 100644 --- a/lambda-events/src/event/cloudwatch_events/emr.rs +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AutoScalingPolicyStateChange { @@ -19,6 +20,7 @@ pub struct AutoScalingPolicyStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClusterStateChange { @@ -37,6 +39,7 @@ pub struct ClusterStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceGroupStateChange { @@ -59,6 +62,7 @@ pub struct InstanceGroupStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StepStatusChange { diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs index 009f1056..95678329 100644 --- a/lambda-events/src/event/cloudwatch_events/gamelift.rs +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -4,6 +4,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingSearching { @@ -20,6 +21,7 @@ pub struct MatchmakingSearching { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Ticket { @@ -35,6 +37,7 @@ pub struct Ticket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Player { @@ -52,6 +55,7 @@ pub struct Player { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GameSessionInfo { @@ -65,6 +69,7 @@ pub struct GameSessionInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct PotentialMatchCreated { @@ -84,6 +89,7 @@ pub struct PotentialMatchCreated { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct RuleEvaluationMetric { @@ -99,6 +105,7 @@ pub struct RuleEvaluationMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AcceptMatch { @@ -115,6 +122,7 @@ pub struct AcceptMatch { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AcceptMatchCompleted { @@ -132,6 +140,7 @@ pub struct AcceptMatchCompleted { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingSucceeded { @@ -148,6 +157,7 @@ pub struct MatchmakingSucceeded { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingTimedOut { @@ -166,6 +176,7 @@ pub struct MatchmakingTimedOut { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingCancelled { @@ -184,6 +195,7 @@ pub struct MatchmakingCancelled { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MatchmakingFailed { diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs index 21669098..69d428b2 100644 --- a/lambda-events/src/event/cloudwatch_events/glue.rs +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobRunStateChange { @@ -19,6 +20,7 @@ pub struct JobRunStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerStarted { @@ -36,6 +38,7 @@ pub struct CrawlerStarted { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerSucceeded { @@ -63,6 +66,7 @@ pub struct CrawlerSucceeded { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CrawlerFailed { @@ -81,6 +85,7 @@ pub struct CrawlerFailed { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobRunStatus { @@ -100,6 +105,7 @@ pub struct JobRunStatus { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct NotificationCondition { @@ -114,6 +120,7 @@ pub struct NotificationCondition { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DataCatalogTableStateChange { @@ -130,6 +137,7 @@ pub struct DataCatalogTableStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DataCatalogDatabaseStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs index c6d8cca1..af5ebfc4 100644 --- a/lambda-events/src/event/cloudwatch_events/health.rs +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Event { @@ -23,6 +24,7 @@ pub struct Event { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventDescription { @@ -37,6 +39,7 @@ pub struct EventDescription { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Entity { diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs index 2a0041de..c96d2be5 100644 --- a/lambda-events/src/event/cloudwatch_events/kms.rs +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CMKEvent { diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs index a652b8b2..7a1a989b 100644 --- a/lambda-events/src/event/cloudwatch_events/macie.rs +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { @@ -34,6 +35,7 @@ pub type BucketScanAlert = Alert; pub type BucketWritableAlert = Alert; pub type BucketContainsHighRiskObjectAlert = Alert; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Trigger { @@ -54,6 +56,7 @@ pub struct Trigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketScanSummary { @@ -83,6 +86,7 @@ pub struct BucketScanSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Ip { @@ -101,6 +105,7 @@ pub struct Ip { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeRange { @@ -116,6 +121,7 @@ pub struct TimeRange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Location { @@ -130,6 +136,7 @@ pub struct Location { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ActionInfo { @@ -146,6 +153,7 @@ pub struct ActionInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketWritableSummary { @@ -170,6 +178,7 @@ pub struct BucketWritableSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Bucket { @@ -184,6 +193,7 @@ pub struct Bucket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Acl { @@ -198,6 +208,7 @@ pub struct Acl { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SecretBucketName { @@ -214,6 +225,7 @@ pub struct SecretBucketName { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Owner { @@ -230,6 +242,7 @@ pub struct Owner { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Grant { @@ -246,6 +259,7 @@ pub struct Grant { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Grantee { @@ -261,6 +275,7 @@ pub struct Grantee { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BucketContainsHighRiskObjectSummary { @@ -289,6 +304,7 @@ pub struct BucketContainsHighRiskObjectSummary { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlertUpdated { @@ -314,6 +330,7 @@ pub struct AlertUpdated { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdatedTrigger { @@ -329,6 +346,7 @@ pub struct UpdatedTrigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct FeatureInfo { diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs index c865b0e0..91f7e7fd 100644 --- a/lambda-events/src/event/cloudwatch_events/mod.rs +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -21,6 +21,7 @@ pub mod trustedadvisor; /// `CloudWatchEvent` is the outer structure of an event sent via CloudWatch Events. /// For examples of events that come via CloudWatch Events, see +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CloudWatchEvent diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs index 30818648..7c26baaf 100644 --- a/lambda-events/src/event/cloudwatch_events/opsworks.rs +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct InstanceStateChange { @@ -26,6 +27,7 @@ pub struct InstanceStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CommandStateChange { @@ -44,6 +46,7 @@ pub struct CommandStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DeploymentStateChange { @@ -65,6 +68,7 @@ pub struct DeploymentStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Alert { diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs index 2eb1ea5e..458787bd 100644 --- a/lambda-events/src/event/cloudwatch_events/signin.rs +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -1,6 +1,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SignIn { @@ -28,6 +29,7 @@ pub struct SignIn { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -44,6 +46,7 @@ pub struct UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ResponseElements { @@ -58,6 +61,7 @@ pub struct ResponseElements { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AdditionalEventData { diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs index 6447887a..e3bfeeb4 100644 --- a/lambda-events/src/event/cloudwatch_events/sms.rs +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct JobStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs index 53a48e10..1bf81ed2 100644 --- a/lambda-events/src/event/cloudwatch_events/ssm.rs +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationStepStatusChange { @@ -33,6 +34,7 @@ pub struct EC2AutomationStepStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2AutomationExecutionStatusChange { @@ -61,6 +63,7 @@ pub struct EC2AutomationExecutionStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StateChange { @@ -76,6 +79,7 @@ pub struct StateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigurationComplianceStateChange { @@ -101,6 +105,7 @@ pub struct ConfigurationComplianceStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTargetRegistration { @@ -118,6 +123,7 @@ pub struct MaintenanceWindowTargetRegistration { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowExecutionStateChange { @@ -139,6 +145,7 @@ pub struct MaintenanceWindowExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTaskExecutionStateChange { @@ -162,6 +169,7 @@ pub struct MaintenanceWindowTaskExecutionStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowTaskTargetInvocationStateChange { @@ -189,6 +197,7 @@ pub struct MaintenanceWindowTaskTargetInvocationStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct MaintenanceWindowStateChange { @@ -204,6 +213,7 @@ pub struct MaintenanceWindowStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ParameterStoreStateChange { @@ -220,6 +230,7 @@ pub struct ParameterStoreStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2CommandStatusChange { @@ -242,6 +253,7 @@ pub struct EC2CommandStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2CommandInvocationStatusChange { @@ -263,6 +275,7 @@ pub struct EC2CommandInvocationStatusChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2StateManagerAssociationStateChange { @@ -299,6 +312,7 @@ pub struct EC2StateManagerAssociationStateChange { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EC2StateManagerInstanceAssociationStateChange { diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs index 08fbe24c..e6d3b96f 100644 --- a/lambda-events/src/event/cloudwatch_events/tag.rs +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TagChangeOnResource { diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs index 19f2aba0..fdaf4a82 100644 --- a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CheckItemRefreshNotification { diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs index 1a485a06..e494918a 100644 --- a/lambda-events/src/event/cloudwatch_logs/mod.rs +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -10,6 +10,7 @@ use serde_json::Value; use std::{fmt, io::BufReader}; /// `LogsEvent` represents the raw event sent by CloudWatch +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct LogsEvent { /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer @@ -25,6 +26,7 @@ pub struct LogsEvent { } /// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event +#[non_exhaustive] #[derive(Clone, Debug, Default, Eq, PartialEq)] pub struct AwsLogs { /// `data` is the log data after is decompressed @@ -32,6 +34,7 @@ pub struct AwsLogs { } /// `LogData` represents the logs group event information +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct LogData { @@ -57,6 +60,7 @@ pub struct LogData { } /// `LogEntry` represents a log entry from cloudwatch logs +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct LogEntry { /// Unique id for the entry diff --git a/lambda-events/src/event/code_commit/mod.rs b/lambda-events/src/event/code_commit/mod.rs index 021f8942..126d7160 100644 --- a/lambda-events/src/event/code_commit/mod.rs +++ b/lambda-events/src/event/code_commit/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; use crate::custom_serde::deserialize_nullish_boolean; /// `CodeCommitEvent` represents a CodeCommit event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitEvent { @@ -23,6 +24,7 @@ pub struct CodeCommitEvent { pub type CodeCommitEventTime = DateTime; /// `CodeCommitRecord` represents a CodeCommit record +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitRecord { @@ -63,6 +65,7 @@ pub struct CodeCommitRecord { } /// `CodeCommitCodeCommit` represents a CodeCommit object in a record +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitCodeCommit { @@ -80,6 +83,7 @@ pub struct CodeCommitCodeCommit { } /// `CodeCommitReference` represents a Reference object in a CodeCommit object +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeCommitReference { diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs index 304cf465..27a0e060 100644 --- a/lambda-events/src/event/codebuild/mod.rs +++ b/lambda-events/src/event/codebuild/mod.rs @@ -12,6 +12,7 @@ pub type CodeBuildPhaseType = String; /// `CodeBuildEvent` is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEvent { @@ -54,6 +55,7 @@ pub struct CodeBuildEvent { } /// `CodeBuildEventDetail` represents the all details related to the code build event +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventDetail { @@ -101,6 +103,7 @@ pub struct CodeBuildEventDetail { } /// `CodeBuildEventAdditionalInformation` represents additional information to the code build event +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEventAdditionalInformation { @@ -133,6 +136,7 @@ pub struct CodeBuildEventAdditionalInformation { } /// `CodeBuildArtifact` represents the artifact provided to build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildArtifact { @@ -154,6 +158,7 @@ pub struct CodeBuildArtifact { } /// `CodeBuildEnvironment` represents the environment for a build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironment { @@ -178,6 +183,7 @@ pub struct CodeBuildEnvironment { } /// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildEnvironmentVariable { @@ -200,6 +206,7 @@ pub struct CodeBuildEnvironmentVariable { } /// `CodeBuildSource` represent the code source will be build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildSource { @@ -217,6 +224,7 @@ pub struct CodeBuildSource { } /// `CodeBuildLogs` gives the log details of a code build +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildLogs { @@ -239,6 +247,7 @@ pub struct CodeBuildLogs { } /// `CodeBuildPhase` represents the phase of a build and its details +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeBuildPhase diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs index 85649729..debe2bf5 100644 --- a/lambda-events/src/event/codedeploy/mod.rs +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -7,6 +7,7 @@ pub type CodeDeployDeploymentState = String; /// `CodeDeployEvent` is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEvent { @@ -49,6 +50,7 @@ pub struct CodeDeployEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodeDeployEventDetail { @@ -81,6 +83,7 @@ pub struct CodeDeployEventDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Deserialize, Serialize, Eq, PartialEq)] #[serde(rename_all = "PascalCase")] pub struct CodeDeployLifecycleEvent { diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs index 3bcc5f2b..087d1452 100644 --- a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -11,6 +11,7 @@ pub type CodePipelineActionState = String; /// CodePipelineEvent is documented at: /// +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineCloudWatchEvent { @@ -53,6 +54,7 @@ pub struct CodePipelineCloudWatchEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetail { @@ -80,6 +82,7 @@ pub struct CodePipelineEventDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineEventDetailType { diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs index 41a9966e..83dfce65 100644 --- a/lambda-events/src/event/codepipeline_job/mod.rs +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJobEvent { @@ -18,6 +19,7 @@ pub struct CodePipelineJobEvent { } /// `CodePipelineJob` represents a job from an AWS CodePipeline event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineJob { @@ -36,6 +38,7 @@ pub struct CodePipelineJob { } /// `CodePipelineData` represents a job from an AWS CodePipeline event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineData { @@ -56,6 +59,7 @@ pub struct CodePipelineData { } /// `CodePipelineActionConfiguration` represents an Action Configuration +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineActionConfiguration { @@ -70,6 +74,7 @@ pub struct CodePipelineActionConfiguration { } /// `CodePipelineConfiguration` represents a configuration for an Action Configuration +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineConfiguration { @@ -89,6 +94,7 @@ pub struct CodePipelineConfiguration { } /// `CodePipelineInputArtifact` represents an input artifact +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputArtifact { @@ -106,6 +112,7 @@ pub struct CodePipelineInputArtifact { } /// `CodePipelineInputLocation` represents a input location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineInputLocation { @@ -123,6 +130,7 @@ pub struct CodePipelineInputLocation { } /// `CodePipelineS3Location` represents an s3 input location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineS3Location { @@ -140,6 +148,7 @@ pub struct CodePipelineS3Location { } /// `CodePipelineOutputArtifact` represents an output artifact +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputArtifact { @@ -157,6 +166,7 @@ pub struct CodePipelineOutputArtifact { } /// `CodePipelineOutputLocation` represents a output location +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineOutputLocation { @@ -174,6 +184,7 @@ pub struct CodePipelineOutputLocation { } /// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CodePipelineArtifactCredentials { diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs index 315bc2ff..d656bb59 100644 --- a/lambda-events/src/event/cognito/mod.rs +++ b/lambda-events/src/event/cognito/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// `CognitoEvent` contains data from an event sent from AWS Cognito Sync +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEvent { @@ -32,6 +33,7 @@ pub struct CognitoEvent { } /// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoDatasetRecord { @@ -52,6 +54,7 @@ pub struct CognitoDatasetRecord { /// `CognitoEventUserPoolsPreSignup` is sent by AWS Cognito User Pools when a user attempts to register /// (sign up), allowing a Lambda to perform custom validation to accept or deny the registration request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignup { @@ -69,6 +72,7 @@ pub struct CognitoEventUserPoolsPreSignup { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreSignupTriggerSource { #[serde(rename = "PreSignUp_SignUp")] @@ -82,6 +86,7 @@ pub enum CognitoEventUserPoolsPreSignupTriggerSource { /// `CognitoEventUserPoolsPreAuthentication` is sent by AWS Cognito User Pools when a user submits their information /// to be authenticated, allowing you to perform custom validations to accept or deny the sign in request. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreAuthentication { @@ -100,6 +105,7 @@ pub struct CognitoEventUserPoolsPreAuthentication { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { #[serde(rename = "PreAuthentication_Authentication")] @@ -109,6 +115,7 @@ pub enum CognitoEventUserPoolsPreAuthenticationTriggerSource { /// `CognitoEventUserPoolsPostConfirmation` is sent by AWS Cognito User Pools after a user is confirmed, /// allowing the Lambda to send custom messages or add custom logic. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostConfirmation @@ -132,6 +139,7 @@ where pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { #[serde(rename = "PostConfirmation_ConfirmForgotPassword")] @@ -143,6 +151,7 @@ pub enum CognitoEventUserPoolsPostConfirmationTriggerSource { /// `CognitoEventUserPoolsPreTokenGen` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGen { @@ -160,6 +169,7 @@ pub struct CognitoEventUserPoolsPreTokenGen { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { #[serde(rename = "TokenGeneration_HostedAuth")] @@ -177,6 +187,7 @@ pub enum CognitoEventUserPoolsPreTokenGenTriggerSource { /// `CognitoEventUserPoolsPostAuthentication` is sent by AWS Cognito User Pools after a user is authenticated, /// allowing the Lambda to add custom logic. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostAuthentication { @@ -195,6 +206,7 @@ pub struct CognitoEventUserPoolsPostAuthentication { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { #[serde(rename = "PostAuthentication_Authentication")] @@ -204,6 +216,7 @@ pub enum CognitoEventUserPoolsPostAuthenticationTriggerSource { /// `CognitoEventUserPoolsMigrateUser` is sent by AWS Cognito User Pools when a user does not exist in the /// user pool at the time of sign-in with a password, or in the forgot-password flow. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUser { @@ -223,6 +236,7 @@ pub struct CognitoEventUserPoolsMigrateUser { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsMigrateUserTriggerSource { #[serde(rename = "UserMigration_Authentication")] @@ -233,6 +247,7 @@ pub enum CognitoEventUserPoolsMigrateUserTriggerSource { } /// `CognitoEventUserPoolsCallerContext` contains information about the caller +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCallerContext { @@ -251,6 +266,7 @@ pub struct CognitoEventUserPoolsCallerContext { } /// `CognitoEventUserPoolsHeader` contains common data from events sent by AWS Cognito User Pools +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsHeader { @@ -271,6 +287,7 @@ pub struct CognitoEventUserPoolsHeader { } /// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignupRequest { @@ -293,6 +310,7 @@ pub struct CognitoEventUserPoolsPreSignupRequest { } /// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreSignupResponse { @@ -309,6 +327,7 @@ pub struct CognitoEventUserPoolsPreSignupResponse { } /// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreAuthenticationRequest { @@ -328,6 +347,7 @@ pub struct CognitoEventUserPoolsPreAuthenticationRequest { } /// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPreAuthenticationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -339,6 +359,7 @@ pub struct CognitoEventUserPoolsPreAuthenticationResponse { pub other: serde_json::Map, } /// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostConfirmationRequest { @@ -358,6 +379,7 @@ pub struct CognitoEventUserPoolsPostConfirmationRequest { } /// `CognitoEventUserPoolsPostConfirmationResponse` contains the response portion of a PostConfirmation event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostConfirmationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -370,6 +392,7 @@ pub struct CognitoEventUserPoolsPostConfirmationResponse { } /// `CognitoEventUserPoolsPreTokenGenRequest` contains request portion of PreTokenGen event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenRequest { @@ -390,6 +413,7 @@ pub struct CognitoEventUserPoolsPreTokenGenRequest { } /// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponse { @@ -405,6 +429,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponse { /// `CognitoEventUserPoolsPreTokenGenV2` is sent by AWS Cognito User Pools when a user attempts to retrieve /// credentials, allowing a Lambda to perform insert, suppress or override claims. This is the Version 2 Payload +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenV2 { @@ -423,6 +448,7 @@ pub struct CognitoEventUserPoolsPreTokenGenV2 { } /// `CognitoEventUserPoolsPreTokenGenRequestV2` contains request portion of PreTokenGenV2 event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { @@ -443,6 +469,7 @@ pub struct CognitoEventUserPoolsPreTokenGenRequestV2 { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { @@ -457,6 +484,7 @@ pub struct CognitoEventUserPoolsPreTokenGenResponseV2 { } /// `ClaimsAndScopeOverrideDetailsV2` allows lambda to add, suppress or override claims in the token +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClaimsAndScopeOverrideDetailsV2 { @@ -473,6 +501,7 @@ pub struct ClaimsAndScopeOverrideDetailsV2 { } /// `CognitoIdTokenGenerationV2` allows lambda to customize the ID Token before generation +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoIdTokenGenerationV2 { @@ -488,6 +517,7 @@ pub struct CognitoIdTokenGenerationV2 { } /// `CognitoAccessTokenGenerationV2` allows lambda to customize the Access Token before generation +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoAccessTokenGenerationV2 { @@ -505,6 +535,7 @@ pub struct CognitoAccessTokenGenerationV2 { } /// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsPostAuthenticationRequest { @@ -525,6 +556,7 @@ pub struct CognitoEventUserPoolsPostAuthenticationRequest { } /// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CognitoEventUserPoolsPostAuthenticationResponse { /// Catchall to catch any additional fields that were present but not explicitly defined by this struct. @@ -536,6 +568,7 @@ pub struct CognitoEventUserPoolsPostAuthenticationResponse { pub other: serde_json::Map, } /// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUserRequest { @@ -557,6 +590,7 @@ pub struct CognitoEventUserPoolsMigrateUserRequest { } /// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsMigrateUserResponse { @@ -581,6 +615,7 @@ pub struct CognitoEventUserPoolsMigrateUserResponse { } /// `ClaimsOverrideDetails` allows lambda to add, suppress or override claims in the token +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClaimsOverrideDetails { @@ -599,6 +634,7 @@ pub struct ClaimsOverrideDetails { } /// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GroupConfiguration { @@ -616,6 +652,7 @@ pub struct GroupConfiguration { /// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication /// process that is underway, along with the corresponding result. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsChallengeResult { @@ -634,6 +671,7 @@ pub struct CognitoEventUserPoolsChallengeResult { } /// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { @@ -656,6 +694,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { } /// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { @@ -675,6 +714,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { } /// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsDefineAuthChallenge { @@ -693,6 +733,7 @@ pub struct CognitoEventUserPoolsDefineAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { #[serde(rename = "DefineAuthChallenge_Authentication")] @@ -701,6 +742,7 @@ pub enum CognitoEventUserPoolsDefineAuthChallengeTriggerSource { } /// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { @@ -725,6 +767,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { } /// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { @@ -746,6 +789,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { } /// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCreateAuthChallenge { @@ -764,6 +808,7 @@ pub struct CognitoEventUserPoolsCreateAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { #[serde(rename = "CreateAuthChallenge_Authentication")] @@ -772,6 +817,7 @@ pub enum CognitoEventUserPoolsCreateAuthChallengeTriggerSource { } /// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeRequest @@ -802,6 +848,7 @@ where } /// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { @@ -818,6 +865,7 @@ pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { /// `CognitoEventUserPoolsVerifyAuthChallenge` sent by AWS Cognito User Pools to verify if the response from the end user /// for a custom Auth Challenge is valid or not +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsVerifyAuthChallenge { @@ -836,6 +884,7 @@ pub struct CognitoEventUserPoolsVerifyAuthChallenge { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { #[serde(rename = "VerifyAuthChallengeResponse_Authentication")] @@ -845,6 +894,7 @@ pub enum CognitoEventUserPoolsVerifyAuthChallengeTriggerSource { /// `CognitoEventUserPoolsCustomMessage` is sent by AWS Cognito User Pools before a verification or MFA message is sent, /// allowing a user to customize the message dynamically. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessage { @@ -862,6 +912,7 @@ pub struct CognitoEventUserPoolsCustomMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, Default)] pub enum CognitoEventUserPoolsCustomMessageTriggerSource { #[serde(rename = "CustomMessage_SignUp")] @@ -882,6 +933,7 @@ pub enum CognitoEventUserPoolsCustomMessageTriggerSource { } /// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessageRequest @@ -910,6 +962,7 @@ where } /// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CognitoEventUserPoolsCustomMessageResponse { diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs index 981419d8..dda9afa3 100644 --- a/lambda-events/src/event/config/mod.rs +++ b/lambda-events/src/event/config/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `ConfigEvent` contains data from an event sent from AWS Config +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigEvent { diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs index 3f15ce0c..b5f5fe3d 100644 --- a/lambda-events/src/event/connect/mod.rs +++ b/lambda-events/src/event/connect/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `ConnectEvent` contains the data structure for a Connect event. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEvent { @@ -25,6 +26,7 @@ pub struct ConnectEvent { } /// `ConnectDetails` holds the details of a Connect event +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectDetails { @@ -45,6 +47,7 @@ pub struct ConnectDetails { } /// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectContactData { @@ -88,6 +91,7 @@ pub struct ConnectContactData { } /// `ConnectEndpoint` represents routing information. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectEndpoint { @@ -107,6 +111,7 @@ pub struct ConnectEndpoint { } /// `ConnectQueue` represents a queue object. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConnectQueue { diff --git a/lambda-events/src/event/documentdb/events/commom_types.rs b/lambda-events/src/event/documentdb/events/commom_types.rs index d9ff1c4d..9624901d 100644 --- a/lambda-events/src/event/documentdb/events/commom_types.rs +++ b/lambda-events/src/event/documentdb/events/commom_types.rs @@ -5,6 +5,7 @@ use serde_json::Value; pub type AnyDocument = HashMap; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DatabaseCollection { @@ -20,6 +21,7 @@ pub struct DatabaseCollection { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentId { #[serde(rename = "_data")] @@ -33,6 +35,7 @@ pub struct DocumentId { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyIdOid { #[serde(rename = "$oid")] @@ -46,6 +49,7 @@ pub struct DocumentKeyIdOid { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentKeyId { #[serde(rename = "_id")] @@ -59,6 +63,7 @@ pub struct DocumentKeyId { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct InnerTimestamp { t: usize, @@ -72,6 +77,7 @@ pub struct InnerTimestamp { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct Timestamp { #[serde(rename = "$timestamp")] diff --git a/lambda-events/src/event/documentdb/events/delete_event.rs b/lambda-events/src/event/documentdb/events/delete_event.rs index 4085a88b..1c2e6afe 100644 --- a/lambda-events/src/event/documentdb/events/delete_event.rs +++ b/lambda-events/src/event/documentdb/events/delete_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDeleteEvent { diff --git a/lambda-events/src/event/documentdb/events/drop_database_event.rs b/lambda-events/src/event/documentdb/events/drop_database_event.rs index fed75259..2506f9cb 100644 --- a/lambda-events/src/event/documentdb/events/drop_database_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_database_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDropDatabaseEvent { diff --git a/lambda-events/src/event/documentdb/events/drop_event.rs b/lambda-events/src/event/documentdb/events/drop_event.rs index 23a8d7a8..42bb566b 100644 --- a/lambda-events/src/event/documentdb/events/drop_event.rs +++ b/lambda-events/src/event/documentdb/events/drop_event.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeDropEvent { diff --git a/lambda-events/src/event/documentdb/events/insert_event.rs b/lambda-events/src/event/documentdb/events/insert_event.rs index aaf82ddc..0c139220 100644 --- a/lambda-events/src/event/documentdb/events/insert_event.rs +++ b/lambda-events/src/event/documentdb/events/insert_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeInsertEvent { diff --git a/lambda-events/src/event/documentdb/events/invalidate_event.rs b/lambda-events/src/event/documentdb/events/invalidate_event.rs index 8e8ef930..f721013e 100644 --- a/lambda-events/src/event/documentdb/events/invalidate_event.rs +++ b/lambda-events/src/event/documentdb/events/invalidate_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeInvalidateEvent { diff --git a/lambda-events/src/event/documentdb/events/rename_event.rs b/lambda-events/src/event/documentdb/events/rename_event.rs index f1761a2a..75797aca 100644 --- a/lambda-events/src/event/documentdb/events/rename_event.rs +++ b/lambda-events/src/event/documentdb/events/rename_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeRenameEvent { diff --git a/lambda-events/src/event/documentdb/events/replace_event.rs b/lambda-events/src/event/documentdb/events/replace_event.rs index b0245241..0ff6ffba 100644 --- a/lambda-events/src/event/documentdb/events/replace_event.rs +++ b/lambda-events/src/event/documentdb/events/replace_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeReplaceEvent { diff --git a/lambda-events/src/event/documentdb/events/update_event.rs b/lambda-events/src/event/documentdb/events/update_event.rs index 5d3795d0..8f6a48a9 100644 --- a/lambda-events/src/event/documentdb/events/update_event.rs +++ b/lambda-events/src/event/documentdb/events/update_event.rs @@ -4,6 +4,7 @@ use serde_json::Value; use super::commom_types::{AnyDocument, DatabaseCollection, DocumentId, DocumentKeyId, Timestamp}; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateTruncate { @@ -18,6 +19,7 @@ pub struct UpdateTruncate { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UpdateDescription { @@ -33,6 +35,7 @@ pub struct UpdateDescription { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ChangeUpdateEvent { diff --git a/lambda-events/src/event/documentdb/mod.rs b/lambda-events/src/event/documentdb/mod.rs index fa753823..c802db62 100644 --- a/lambda-events/src/event/documentdb/mod.rs +++ b/lambda-events/src/event/documentdb/mod.rs @@ -9,6 +9,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(tag = "operationType", rename_all = "camelCase")] pub enum ChangeEvent { @@ -22,6 +23,7 @@ pub enum ChangeEvent { Rename(ChangeRenameEvent), } +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] pub struct DocumentDbInnerEvent { pub event: ChangeEvent, @@ -34,6 +36,7 @@ pub struct DocumentDbInnerEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct DocumentDbEvent { diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs index 316768dc..62872f3e 100644 --- a/lambda-events/src/event/dynamodb/mod.rs +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -12,8 +12,8 @@ use std::fmt; #[cfg(test)] mod attributes; -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamViewType { NewImage, @@ -35,8 +35,8 @@ impl fmt::Display for StreamViewType { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum StreamStatus { Enabling, @@ -58,8 +58,8 @@ impl fmt::Display for StreamStatus { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum SharedIteratorType { TrimHorizon, @@ -81,8 +81,8 @@ impl fmt::Display for SharedIteratorType { } } -#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum OperationType { #[default] @@ -102,8 +102,8 @@ impl fmt::Display for OperationType { } } -#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[non_exhaustive] +#[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KeyType { #[default] @@ -123,6 +123,7 @@ impl fmt::Display for KeyType { /// The `Event` stream event handled to Lambda /// +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] pub struct Event { #[serde(rename = "Records")] @@ -138,6 +139,7 @@ pub struct Event { /// `TimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEvent { @@ -157,6 +159,7 @@ pub struct TimeWindowEvent { } /// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponse { @@ -174,6 +177,7 @@ pub struct TimeWindowEventResponse { } /// EventRecord stores information about each record of a DynamoDb stream event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct EventRecord { @@ -240,6 +244,7 @@ pub struct EventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -258,6 +263,7 @@ pub struct UserIdentity { /// `DynamoDbStreamRecord` represents a description of a single data modification that was performed on an item /// in a DynamoDB table. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct StreamRecord { diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs index e3ff7ff8..b28678f4 100644 --- a/lambda-events/src/event/ecr_scan/mod.rs +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEvent { @@ -31,6 +32,7 @@ pub struct EcrScanEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventDetailType { @@ -56,6 +58,7 @@ pub struct EcrScanEventDetailType { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct EcrScanEventFindingSeverityCounts { diff --git a/lambda-events/src/event/eventbridge/mod.rs b/lambda-events/src/event/eventbridge/mod.rs index f2d86550..824de1ef 100644 --- a/lambda-events/src/event/eventbridge/mod.rs +++ b/lambda-events/src/event/eventbridge/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; /// Deserialize the event detail into a structure that's `DeserializeOwned`. /// /// See for structure details. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T1: DeserializeOwned"))] #[serde(rename_all = "kebab-case")] diff --git a/lambda-events/src/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs index a97577f5..d0dec03d 100644 --- a/lambda-events/src/event/firehose/mod.rs +++ b/lambda-events/src/event/firehose/mod.rs @@ -8,6 +8,7 @@ use serde_json::Value; use std::collections::HashMap; /// `KinesisFirehoseEvent` represents the input event from Amazon Kinesis Firehose. It is used as the input parameter. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEvent { @@ -31,6 +32,7 @@ pub struct KinesisFirehoseEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseEventRecord { @@ -49,6 +51,7 @@ pub struct KinesisFirehoseEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponse { @@ -62,6 +65,7 @@ pub struct KinesisFirehoseResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecord { @@ -81,6 +85,7 @@ pub struct KinesisFirehoseResponseRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseResponseRecordMetadata { @@ -96,6 +101,7 @@ pub struct KinesisFirehoseResponseRecordMetadata { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisFirehoseRecordMetadata { diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 42c8a9c7..29ef203e 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -8,6 +8,7 @@ use serde::{ }; /// `IamPolicyDocument` represents an IAM policy document. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyDocument { @@ -47,6 +48,7 @@ pub struct IamPolicyStatement { pub type IamPolicyCondition = HashMap>>; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub enum IamPolicyEffect { #[default] diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs index f544510f..e1d7b504 100644 --- a/lambda-events/src/event/iot/mod.rs +++ b/lambda-events/src/event/iot/mod.rs @@ -6,6 +6,7 @@ use serde_json::Value; /// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. /// See +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerRequest { @@ -24,6 +25,7 @@ pub struct IoTCoreCustomAuthorizerRequest { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreProtocolData { @@ -39,6 +41,7 @@ pub struct IoTCoreProtocolData { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreTlsContext { @@ -53,6 +56,7 @@ pub struct IoTCoreTlsContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreHttpContext { @@ -70,6 +74,7 @@ pub struct IoTCoreHttpContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreMqttContext { @@ -87,6 +92,7 @@ pub struct IoTCoreMqttContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreConnectionMetadata { @@ -103,6 +109,7 @@ pub struct IoTCoreConnectionMetadata { /// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. /// See +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCoreCustomAuthorizerResponse { diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs index 50338120..a88041c0 100644 --- a/lambda-events/src/event/iot_1_click/mod.rs +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -7,6 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `IoTOneClickEvent` represents a click event published by clicking button type /// device. +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickEvent { @@ -22,6 +23,7 @@ pub struct IoTOneClickEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceEvent { @@ -35,6 +37,7 @@ pub struct IoTOneClickDeviceEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickButtonClicked { @@ -51,6 +54,7 @@ pub struct IoTOneClickButtonClicked { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickDeviceInfo { @@ -71,6 +75,7 @@ pub struct IoTOneClickDeviceInfo { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTOneClickPlacementInfo { diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs index 9a7aaec3..70a24f9d 100644 --- a/lambda-events/src/event/iot_button/mod.rs +++ b/lambda-events/src/event/iot_button/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTButtonEvent { diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs index 30142606..22b13db9 100644 --- a/lambda-events/src/event/iot_deprecated/mod.rs +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -5,6 +5,7 @@ use serde_json::Value; /// `IoTCustomAuthorizerRequest` contains data coming in to a custom IoT device gateway authorizer function. /// Deprecated: Use IoTCoreCustomAuthorizerRequest instead. `IoTCustomAuthorizerRequest` does not correctly model the request schema +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerRequest { @@ -33,6 +34,7 @@ pub type IoTTlsContext = IoTCoreTlsContext; /// `IoTCustomAuthorizerResponse` represents the expected format of an IoT device gateway authorization response. /// Deprecated: Use IoTCoreCustomAuthorizerResponse. `IoTCustomAuthorizerResponse` does not correctly model the response schema. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct IoTCustomAuthorizerResponse { diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs index 7332b1e0..ecd02d87 100644 --- a/lambda-events/src/event/kafka/mod.rs +++ b/lambda-events/src/event/kafka/mod.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use std::collections::HashMap; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaEvent { @@ -25,6 +26,7 @@ pub struct KafkaEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KafkaRecord { diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs index e9f8f676..9ca62f69 100644 --- a/lambda-events/src/event/kinesis/analytics.rs +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryEvent { @@ -20,6 +21,7 @@ pub struct KinesisAnalyticsOutputDeliveryEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryEventRecord { @@ -35,6 +37,7 @@ pub struct KinesisAnalyticsOutputDeliveryEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponse { @@ -48,6 +51,7 @@ pub struct KinesisAnalyticsOutputDeliveryResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisAnalyticsOutputDeliveryResponseRecord { diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs index 6bfb2bea..215e9288 100644 --- a/lambda-events/src/event/kinesis/event.rs +++ b/lambda-events/src/event/kinesis/event.rs @@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Clone, Default, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEvent { @@ -22,6 +23,7 @@ pub struct KinesisEvent { /// `KinesisTimeWindowEvent` represents an Amazon Dynamodb event when using time windows /// ref. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEvent { @@ -41,6 +43,7 @@ pub struct KinesisTimeWindowEvent { } /// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisTimeWindowEventResponse { @@ -57,6 +60,7 @@ pub struct KinesisTimeWindowEventResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventRecord { @@ -89,6 +93,7 @@ pub struct KinesisEventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisRecord { @@ -111,6 +116,7 @@ pub struct KinesisRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum KinesisEncryptionType { diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs index 646d141a..f5771623 100644 --- a/lambda-events/src/event/lambda_function_urls/mod.rs +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::{deserialize_lambda_map, serialize_headers}; /// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequest { @@ -37,6 +38,7 @@ pub struct LambdaFunctionUrlRequest { } /// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContext { @@ -69,6 +71,7 @@ pub struct LambdaFunctionUrlRequestContext { } /// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { @@ -83,6 +86,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { } /// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { @@ -106,6 +110,7 @@ pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { } /// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlRequestContextHttpDescription { @@ -129,6 +134,7 @@ pub struct LambdaFunctionUrlRequestContextHttpDescription { } /// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LambdaFunctionUrlResponse { diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs index 46258951..b64279d7 100644 --- a/lambda-events/src/event/lex/mod.rs +++ b/lambda-events/src/event/lex/mod.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexEvent { @@ -31,6 +32,7 @@ pub struct LexEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexBot { @@ -46,6 +48,7 @@ pub struct LexBot { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexCurrentIntent { @@ -65,6 +68,7 @@ pub struct LexCurrentIntent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexAlternativeIntents { @@ -84,6 +88,7 @@ pub struct LexAlternativeIntents { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SlotDetail { @@ -98,6 +103,7 @@ pub struct SlotDetail { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexDialogAction { @@ -123,6 +129,7 @@ pub type SessionAttributes = HashMap; pub type Slots = HashMap>; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponse { @@ -137,6 +144,7 @@ pub struct LexResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct LexResponseCard { @@ -152,6 +160,7 @@ pub struct LexResponseCard { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct Attachment { diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs index e1a7256b..5b75e3c2 100644 --- a/lambda-events/src/event/rabbitmq/mod.rs +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqEvent { @@ -24,6 +25,7 @@ pub struct RabbitMqEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqMessage { @@ -40,6 +42,7 @@ pub struct RabbitMqMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RabbitMqBasicProperties diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs index 133960f3..9b8f419b 100644 --- a/lambda-events/src/event/s3/batch_job.rs +++ b/lambda-events/src/event/s3/batch_job.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `S3BatchJobEvent` encapsulates the detail of a s3 batch job +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobEvent { @@ -22,6 +23,7 @@ pub struct S3BatchJobEvent { } /// `S3BatchJob` whichs have the job id +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJob { @@ -37,6 +39,7 @@ pub struct S3BatchJob { } /// `S3BatchJobTask` represents one task in the s3 batch job and have all task details +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobTask { @@ -58,6 +61,7 @@ pub struct S3BatchJobTask { } /// `S3BatchJobResponse` is the response of a iven s3 batch job with the results +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobResponse { @@ -78,6 +82,7 @@ pub struct S3BatchJobResponse { } /// `S3BatchJobResult` represents the result of a given task +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3BatchJobResult { diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs index 46a334db..68b9aee7 100644 --- a/lambda-events/src/event/s3/event.rs +++ b/lambda-events/src/event/s3/event.rs @@ -7,6 +7,7 @@ use std::collections::HashMap; use crate::custom_serde::deserialize_lambda_map; /// `S3Event` which wrap an array of `S3Event`Record +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Event { @@ -22,6 +23,7 @@ pub struct S3Event { } /// `S3EventRecord` which wrap record data +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3EventRecord { @@ -50,6 +52,7 @@ pub struct S3EventRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3UserIdentity { @@ -64,6 +67,7 @@ pub struct S3UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3RequestParameters { @@ -79,6 +83,7 @@ pub struct S3RequestParameters { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Entity { @@ -98,6 +103,7 @@ pub struct S3Entity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Bucket { @@ -116,6 +122,7 @@ pub struct S3Bucket { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3Object { diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs index 3b01fe73..be52e1f8 100644 --- a/lambda-events/src/event/s3/object_lambda.rs +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -7,6 +7,7 @@ use crate::custom_serde::{deserialize_headers, serialize_headers}; /// `S3ObjectLambdaEvent` contains data coming from S3 object lambdas /// See: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct S3ObjectLambdaEvent

@@ -35,6 +36,7 @@ where /// `GetObjectContext` contains the input and output details /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct GetObjectContext { @@ -52,6 +54,7 @@ pub struct GetObjectContext { /// `HeadObjectContext` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct HeadObjectContext { @@ -67,6 +70,7 @@ pub struct HeadObjectContext { /// `ListObjectsContext` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ListObjectsContext { @@ -82,6 +86,7 @@ pub struct ListObjectsContext { /// `ListObjectsV2Context` /// for connections to Amazon S3 and S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ListObjectsV2Context { @@ -96,6 +101,7 @@ pub struct ListObjectsV2Context { } /// `Configuration` contains information about the Object Lambda access point +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Configuration

@@ -117,6 +123,7 @@ where } /// `UserRequest` contains information about the original call to S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserRequest { @@ -134,6 +141,7 @@ pub struct UserRequest { } /// `UserIdentity` contains details about the identity that made the call to S3 Object Lambda +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct UserIdentity { @@ -152,6 +160,7 @@ pub struct UserIdentity { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionContext { @@ -167,6 +176,7 @@ pub struct SessionContext { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SessionIssuer { diff --git a/lambda-events/src/event/secretsmanager/mod.rs b/lambda-events/src/event/secretsmanager/mod.rs index fc883e52..9502d654 100644 --- a/lambda-events/src/event/secretsmanager/mod.rs +++ b/lambda-events/src/event/secretsmanager/mod.rs @@ -2,6 +2,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "catch-all-fields")] use serde_json::Value; +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SecretsManagerSecretRotationEvent { diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs index 20498780..31a55ea3 100644 --- a/lambda-events/src/event/ses/mod.rs +++ b/lambda-events/src/event/ses/mod.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `SimpleEmailEvent` is the outer structure of an event sent via SES. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailEvent { @@ -18,6 +19,7 @@ pub struct SimpleEmailEvent { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailRecord { @@ -35,6 +37,7 @@ pub struct SimpleEmailRecord { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailService { @@ -50,6 +53,7 @@ pub struct SimpleEmailService { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailMessage { @@ -71,6 +75,7 @@ pub struct SimpleEmailMessage { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceipt { @@ -94,6 +99,7 @@ pub struct SimpleEmailReceipt { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailHeader { @@ -110,6 +116,7 @@ pub struct SimpleEmailHeader { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailCommonHeaders { @@ -136,6 +143,7 @@ pub struct SimpleEmailCommonHeaders { /// Types. For example, the FunctionARN and InvocationType fields are only /// present for the Lambda Type, and the BucketName and ObjectKey fields are only /// present for the S3 Type. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailReceiptAction { @@ -160,6 +168,7 @@ pub struct SimpleEmailReceiptAction { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailVerdict { @@ -177,6 +186,7 @@ pub struct SimpleEmailVerdict { pub type SimpleEmailDispositionValue = String; /// `SimpleEmailDisposition` disposition return for SES to control rule functions +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SimpleEmailDisposition { diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs index 163c13ac..d40dd45c 100644 --- a/lambda-events/src/event/sns/mod.rs +++ b/lambda-events/src/event/sns/mod.rs @@ -9,6 +9,7 @@ use crate::custom_serde::{deserialize_lambda_map, deserialize_nullish_boolean}; /// The `Event` notification event handled by Lambda /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsEvent { @@ -24,6 +25,7 @@ pub struct SnsEvent { } /// SnsRecord stores information about each record of a SNS event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsRecord { @@ -49,6 +51,7 @@ pub struct SnsRecord { } /// SnsMessage stores information about each record of a SNS event +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SnsMessage { @@ -107,6 +110,7 @@ pub struct SnsMessage { /// An alternate `Event` notification event to use alongside `SnsRecordObj` and `SnsMessageObj` if you want to deserialize an object inside your SNS messages rather than getting an `Option` message /// /// [https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html](https://docs.aws.amazon.com/lambda/latest/dg/with-sns.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -123,6 +127,7 @@ pub struct SnsEventObj { } /// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -149,6 +154,7 @@ pub struct SnsRecordObj { } /// Alternate version of `SnsMessage` to use in conjunction with `SnsEventObj` and `SnsRecordObj` for deserializing the message into a struct of type `T` +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] @@ -213,6 +219,7 @@ pub struct SnsMessageObj { /// Message attributes are optional and separate from—but are sent together with—the message body. The receiver can use this information to decide how to handle the message without having to process the message body first. /// /// Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct MessageAttribute { /// The data type of the attribute. Per the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html), lambda notifications, this will only be **String** or **Binary**. @@ -232,6 +239,7 @@ pub struct MessageAttribute { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchAlarmPayload { @@ -255,6 +263,7 @@ pub struct CloudWatchAlarmPayload { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchAlarmTrigger { @@ -282,6 +291,7 @@ pub struct CloudWatchAlarmTrigger { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetricDataQuery { @@ -301,6 +311,7 @@ pub struct CloudWatchMetricDataQuery { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetricStat { @@ -317,6 +328,7 @@ pub struct CloudWatchMetricStat { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct CloudWatchMetric { @@ -333,6 +345,7 @@ pub struct CloudWatchMetric { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] pub struct CloudWatchDimension { pub name: String, diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs index 64a368ca..0a380734 100644 --- a/lambda-events/src/event/sqs/mod.rs +++ b/lambda-events/src/event/sqs/mod.rs @@ -5,6 +5,7 @@ use serde_json::Value; use std::collections::HashMap; /// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEvent { @@ -20,6 +21,7 @@ pub struct SqsEvent { } /// An individual SQS Message, its metadata, and Message Attributes +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsMessage { @@ -57,6 +59,7 @@ pub struct SqsMessage { } /// Alternative to `SqsEvent` to be used alongside `SqsMessageObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -74,6 +77,7 @@ pub struct SqsEventObj { } /// Alternative to `SqsMessage` to be used alongside `SqsEventObj` when you need to deserialize a nested object into a struct of type `T` within the SQS Message rather than just using the raw SQS Message string +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -115,6 +119,7 @@ pub struct SqsMessageObj { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsMessageAttribute { @@ -135,6 +140,7 @@ pub struct SqsMessageAttribute { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchResponse { @@ -148,6 +154,7 @@ pub struct SqsBatchResponse { pub other: serde_json::Map, } +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct BatchItemFailure { @@ -162,6 +169,7 @@ pub struct BatchItemFailure { } /// The Event sent to Lambda from the SQS API. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -178,6 +186,7 @@ pub struct SqsApiEventObj { } /// The Event sent to Lambda from SQS API. Contains 1 or more individual SQS Messages +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsApiEvent { @@ -194,6 +203,7 @@ pub struct SqsApiEvent { /// Alternative to SqsApiEvent to be used alongside `SqsApiMessageObj` when you need to /// deserialize a nested object into a struct of type T within the SQS Message rather /// than just using the raw SQS Message string +#[non_exhaustive] #[serde_with::serde_as] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(bound(deserialize = "T: DeserializeOwned"))] @@ -228,6 +238,7 @@ pub struct SqsApiMessageObj { } /// An individual SQS API Message, its metadata, and Message Attributes +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct SqsApiMessage { diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs index caf2c02d..9f2391c8 100644 --- a/lambda-events/src/event/streams/mod.rs +++ b/lambda-events/src/event/streams/mod.rs @@ -3,6 +3,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; /// `KinesisEventResponse` is the outer structure to report batch item failures for KinesisEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisEventResponse { @@ -17,6 +18,7 @@ pub struct KinesisEventResponse { } /// `KinesisBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct KinesisBatchItemFailure { @@ -32,6 +34,7 @@ pub struct KinesisBatchItemFailure { } /// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbEventResponse { @@ -46,6 +49,7 @@ pub struct DynamoDbEventResponse { } /// `DynamoDbBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct DynamoDbBatchItemFailure { @@ -61,6 +65,7 @@ pub struct DynamoDbBatchItemFailure { } /// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsEventResponse { @@ -75,6 +80,7 @@ pub struct SqsEventResponse { } /// `SqsBatchItemFailure` is the individual record which failed processing. +#[non_exhaustive] #[derive(Debug, Default, Clone, Eq, PartialEq, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct SqsBatchItemFailure { diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs index 424050ab..d67a8e91 100644 --- a/lambda-events/src/time_window.rs +++ b/lambda-events/src/time_window.rs @@ -7,6 +7,7 @@ use crate::custom_serde::deserialize_lambda_map; /// `Window` is the object that captures the time window for the records in the event when using the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct Window { @@ -26,6 +27,7 @@ impl Default for Window { /// `TimeWindowProperties` is the object that captures properties that relate to the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowProperties { @@ -53,6 +55,7 @@ pub struct TimeWindowProperties { /// `TimeWindowEventResponseProperties` is the object that captures response properties that relate to the tumbling windows feature /// Kinesis: /// DDB: +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TimeWindowEventResponseProperties { diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 8b868d25..9b6208a3 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -62,6 +62,7 @@ impl LambdaResponse { Body::Empty => (false, None), b @ Body::Text(_) => (false, Some(b)), b @ Body::Binary(_) => (true, Some(b)), + _ => (false, None), }; let headers = parts.headers; @@ -79,7 +80,7 @@ impl LambdaResponse { multi_value_headers: headers, // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + other: Default::default() }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { From bfc73a63b44582f64b01cd5be506f4d81be313d3 Mon Sep 17 00:00:00 2001 From: mpindaru <102007013+mpindaru@users.noreply.github.com> Date: Fri, 10 Oct 2025 17:21:36 +0100 Subject: [PATCH 211/211] feat(lambda-events, lambda-http): mark all public structs/enums as #[non_exhaustive] (#1046) Co-authored-by: Mara Pindaru --- lambda-events/src/event/alb/mod.rs | 1 + lambda-events/src/event/apigw/mod.rs | 4 ++ lambda-events/src/event/iam/mod.rs | 1 + lambda-http/src/ext/extensions.rs | 4 ++ lambda-http/src/ext/request.rs | 6 +- lambda-http/src/lib.rs | 2 + lambda-http/src/request.rs | 3 + lambda-http/src/response.rs | 81 +++++++++++++--------- lambda-http/src/streaming.rs | 2 + lambda-integration-tests/src/authorizer.rs | 44 +++++++----- lambda-integration-tests/src/helloworld.rs | 18 +++-- 11 files changed, 108 insertions(+), 58 deletions(-) diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs index 2e89f588..1829bf01 100644 --- a/lambda-events/src/event/alb/mod.rs +++ b/lambda-events/src/event/alb/mod.rs @@ -77,6 +77,7 @@ pub struct ElbContext { } /// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AlbTargetGroupResponse { diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs index 199447ec..015eff40 100644 --- a/lambda-events/src/event/apigw/mod.rs +++ b/lambda-events/src/event/apigw/mod.rs @@ -58,6 +58,7 @@ pub struct ApiGatewayProxyRequest { } /// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayProxyResponse { @@ -349,6 +350,7 @@ pub struct ApiGatewayV2httpRequestContextHttpDescription { } /// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayV2httpResponse { @@ -897,6 +899,7 @@ pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { } /// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApiGatewayCustomAuthorizerResponse @@ -963,6 +966,7 @@ where } /// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct ApiGatewayCustomAuthorizerPolicy { diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs index 29ef203e..fd190950 100644 --- a/lambda-events/src/event/iam/mod.rs +++ b/lambda-events/src/event/iam/mod.rs @@ -25,6 +25,7 @@ pub struct IamPolicyDocument { } /// `IamPolicyStatement` represents one statement from IAM policy with action, effect and resource +#[non_exhaustive] #[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] #[serde(rename_all = "PascalCase")] pub struct IamPolicyStatement { diff --git a/lambda-http/src/ext/extensions.rs b/lambda-http/src/ext/extensions.rs index cfbdaec2..65bf8ac0 100644 --- a/lambda-http/src/ext/extensions.rs +++ b/lambda-http/src/ext/extensions.rs @@ -7,12 +7,14 @@ use lambda_runtime::Context; use crate::request::RequestContext; /// ALB/API gateway pre-parsed http query string parameters +#[non_exhaustive] #[derive(Clone)] pub(crate) struct QueryStringParameters(pub(crate) QueryMap); /// API gateway pre-extracted url path parameters /// /// These will always be empty for ALB requests +#[non_exhaustive] #[derive(Clone)] pub(crate) struct PathParameters(pub(crate) QueryMap); @@ -20,10 +22,12 @@ pub(crate) struct PathParameters(pub(crate) QueryMap); /// [stage variables](https://docs.aws.amazon.com/apigateway/latest/developerguide/stage-variables.html) /// /// These will always be empty for ALB requests +#[non_exhaustive] #[derive(Clone)] pub(crate) struct StageVariables(pub(crate) QueryMap); /// ALB/API gateway raw http path without any stage information +#[non_exhaustive] #[derive(Clone)] pub(crate) struct RawHttpPath(pub(crate) String); diff --git a/lambda-http/src/ext/request.rs b/lambda-http/src/ext/request.rs index dc14532e..38e45afa 100644 --- a/lambda-http/src/ext/request.rs +++ b/lambda-http/src/ext/request.rs @@ -12,8 +12,8 @@ use crate::Body; /// Request payload deserialization errors /// /// Returned by [`RequestPayloadExt::payload()`] -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum PayloadError { /// Returned when `application/json` bodies fail to deserialize a payload Json(serde_json::Error), @@ -22,16 +22,16 @@ pub enum PayloadError { } /// Indicates a problem processing a JSON payload. -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum JsonPayloadError { /// Problem deserializing a JSON payload. Parsing(serde_json::Error), } /// Indicates a problem processing an x-www-form-urlencoded payload. -#[derive(Debug)] #[non_exhaustive] +#[derive(Debug)] pub enum FormUrlEncodedPayloadError { /// Problem deserializing an x-www-form-urlencoded payload. Parsing(SerdeError), diff --git a/lambda-http/src/lib.rs b/lambda-http/src/lib.rs index 36e2ffbd..60e279c7 100644 --- a/lambda-http/src/lib.rs +++ b/lambda-http/src/lib.rs @@ -110,6 +110,7 @@ pub type Request = http::Request; /// Future that will convert an [`IntoResponse`] into an actual [`LambdaResponse`] /// /// This is used by the `Adapter` wrapper and is completely internal to the `lambda_http::run` function. +#[non_exhaustive] #[doc(hidden)] pub enum TransformResponse<'a, R, E> { Request(RequestOrigin, RequestFuture<'a, R, E>), @@ -143,6 +144,7 @@ where /// Wraps a `Service` in a `Service>` /// /// This is completely internal to the `lambda_http::run` function. +#[non_exhaustive] #[doc(hidden)] pub struct Adapter<'a, R, S> { service: S, diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index a9281b46..fc88fc4a 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -39,6 +39,7 @@ use url::Url; /// /// This is not intended to be a type consumed by crate users directly. The order /// of the variants are notable. Serde will try to deserialize in this order. +#[non_exhaustive] #[doc(hidden)] #[derive(Debug)] pub enum LambdaRequest { @@ -85,6 +86,7 @@ impl LambdaRequest { pub type RequestFuture<'a, R, E> = Pin> + Send + 'a>>; /// Represents the origin from which the lambda was requested from. +#[non_exhaustive] #[doc(hidden)] #[derive(Debug, Clone)] pub enum RequestOrigin { @@ -388,6 +390,7 @@ fn apigw_path_with_stage(stage: &Option, path: &str) -> String { /// Event request context as an enumeration of request contexts /// for both ALB and API Gateway and HTTP API events +#[non_exhaustive] #[derive(Deserialize, Debug, Clone, Serialize)] #[serde(untagged)] pub enum RequestContext { diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 9b6208a3..6fad374b 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -40,6 +40,7 @@ const TEXT_ENCODING_PREFIXES: [&str; 5] = [ const TEXT_ENCODING_SUFFIXES: [&str; 3] = ["+xml", "+yaml", "+json"]; /// Representation of Lambda response +#[non_exhaustive] #[doc(hidden)] #[derive(Serialize, Debug)] #[serde(untagged)] @@ -70,17 +71,22 @@ impl LambdaResponse { match request_origin { #[cfg(feature = "apigw_rest")] - RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { - body, - is_base64_encoded, - status_code: status_code as i64, + RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1({ + let mut response = ApiGatewayProxyResponse::default(); + + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // Explicitly empty, as API gateway v1 will merge "headers" and // "multi_value_headers" fields together resulting in duplicate response headers. - headers: HeaderMap::new(), - multi_value_headers: headers, + response.headers = HeaderMap::new(); + response.multi_value_headers = headers; // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default() + { + response.other = Default::default(); + } + response }), #[cfg(feature = "apigw_http")] RequestOrigin::ApiGatewayV2 => { @@ -96,51 +102,64 @@ impl LambdaResponse { .collect(); headers.remove(SET_COOKIE); - LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse { - body, - is_base64_encoded, - status_code: status_code as i64, - cookies, + LambdaResponse::ApiGatewayV2({ + let mut response = ApiGatewayV2httpResponse::default(); + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; + response.cookies = cookies; // API gateway v2 doesn't have "multi_value_headers" field. Duplicate headers // are combined with commas and included in the headers field. - headers, - multi_value_headers: HeaderMap::new(), + response.headers = headers; + response.multi_value_headers = HeaderMap::new(); // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }) } #[cfg(feature = "alb")] - RequestOrigin::Alb => LambdaResponse::Alb(AlbTargetGroupResponse { - body, - status_code: status_code as i64, - is_base64_encoded, + RequestOrigin::Alb => LambdaResponse::Alb({ + let mut response = AlbTargetGroupResponse::default(); + + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // ALB responses are used for ALB integration, which can be configured to use // either "headers" or "multi_value_headers" field. We need to return both fields // to ensure both configuration work correctly. - headers: headers.clone(), - multi_value_headers: headers, - status_description: Some(format!( + response.headers = headers.clone(); + response.multi_value_headers = headers; + response.status_description = Some(format!( "{} {}", status_code, parts.status.canonical_reason().unwrap_or_default() - )), + )); // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }), #[cfg(feature = "apigw_websockets")] - RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { - body, - is_base64_encoded, - status_code: status_code as i64, + RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1({ + let mut response = ApiGatewayProxyResponse::default(); + response.body = body; + response.is_base64_encoded = is_base64_encoded; + response.status_code = status_code as i64; // Explicitly empty, as API gateway v1 will merge "headers" and // "multi_value_headers" fields together resulting in duplicate response headers. - headers: HeaderMap::new(), - multi_value_headers: headers, + response.headers = HeaderMap::new(); + response.multi_value_headers = headers; // Today, this implementation doesn't provide any additional fields #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }), #[cfg(feature = "pass_through")] RequestOrigin::PassThrough => { diff --git a/lambda-http/src/streaming.rs b/lambda-http/src/streaming.rs index 6dd17230..ed61c773 100644 --- a/lambda-http/src/streaming.rs +++ b/lambda-http/src/streaming.rs @@ -21,6 +21,7 @@ use std::{future::Future, marker::PhantomData}; /// An adapter that lifts a standard [`Service`] into a /// [`Service>`] which produces streaming Lambda HTTP /// responses. +#[non_exhaustive] pub struct StreamAdapter<'a, S, B> { service: S, _phantom_data: PhantomData<&'a B>, @@ -147,6 +148,7 @@ where } pin_project_lite::pin_project! { +#[non_exhaustive] pub struct BodyStream { #[pin] pub(crate) body: B, diff --git a/lambda-integration-tests/src/authorizer.rs b/lambda-integration-tests/src/authorizer.rs index b8dc3782..23f17b29 100644 --- a/lambda-integration-tests/src/authorizer.rs +++ b/lambda-integration-tests/src/authorizer.rs @@ -34,26 +34,36 @@ async fn func( } fn allow(method_arn: &str) -> ApiGatewayCustomAuthorizerResponse { - let stmt = IamPolicyStatement { - action: vec!["execute-api:Invoke".to_string()], - resource: vec![method_arn.to_owned()], - effect: aws_lambda_events::iam::IamPolicyEffect::Allow, - condition: None, + let stmt = { + let mut statement = IamPolicyStatement::default(); + statement.action = vec!["execute-api:Invoke".to_string()]; + statement.resource = vec![method_arn.to_owned()]; + statement.effect = aws_lambda_events::iam::IamPolicyEffect::Allow; + statement.condition = None; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + statement.other = Default::default(); + } + statement }; - let policy = ApiGatewayCustomAuthorizerPolicy { - version: Some("2012-10-17".to_string()), - statement: vec![stmt], + let policy = { + let mut policy = ApiGatewayCustomAuthorizerPolicy::default(); + policy.version = Some("2012-10-17".to_string()); + policy.statement = vec![stmt]; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + policy.other = Default::default(); + } + policy }; - ApiGatewayCustomAuthorizerResponse { - principal_id: Some("user".to_owned()), - policy_document: policy, - context: json!({ "hello": "world" }), - usage_identifier_key: None, - #[cfg(feature = "catch-all-fields")] - other: Default::default(), + let mut response = ApiGatewayCustomAuthorizerResponse::default(); + response.principal_id = Some("user".to_owned()); + response.policy_document = policy; + response.context = json!({ "hello": "world" }); + response.usage_identifier_key = None; + #[cfg(feature = "catch-all-fields")] + { + response.other = Default::default(); } + response } diff --git a/lambda-integration-tests/src/helloworld.rs b/lambda-integration-tests/src/helloworld.rs index c3a74f8c..2eafc409 100644 --- a/lambda-integration-tests/src/helloworld.rs +++ b/lambda-integration-tests/src/helloworld.rs @@ -16,14 +16,18 @@ async fn main() -> Result<(), Error> { async fn func(_event: LambdaEvent) -> Result { let mut headers = HeaderMap::new(); headers.insert("content-type", "text/html".parse().unwrap()); - let resp = ApiGatewayProxyResponse { - status_code: 200, - multi_value_headers: headers.clone(), - is_base64_encoded: false, - body: Some("Hello world!".into()), - headers, + let resp = { + let mut response = ApiGatewayProxyResponse::default(); + response.status_code = 200; + response.multi_value_headers = headers.clone(); + response.is_base64_encoded = false; + response.body = Some("Hello world!".into()); + response.headers = headers; #[cfg(feature = "catch-all-fields")] - other: Default::default(), + { + response.other = Default::default(); + } + response }; Ok(resp) }