From 3df38b6e2f4932cc28b1bce0e320678af6600531 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 4 May 2023 19:42:38 -0700 Subject: [PATCH] Bring aws_lambda_events as subpackage Signed-off-by: David Calavera --- .github/actions/rust-build/action.yml | 26 + .github/workflows/build-events.yml | 28 + .github/workflows/build-extension.yml | 39 + .github/workflows/build-runtime.yml | 45 + .github/workflows/build.yml | 93 -- .github/workflows/check-examples.yml | 16 + .github/workflows/format.yml | 55 ++ Cargo.toml | 3 +- README.md | 1 + .../Cargo.toml | 4 +- examples/basic-s3-thumbnail/Cargo.toml | 2 +- examples/basic-sqs/Cargo.toml | 2 +- lambda-events/Cargo.toml | 117 +++ lambda-events/README.md | 34 + .../src/custom_serde/codebuild_time.rs | 105 ++ .../src/custom_serde/float_unix_epoch.rs | 109 +++ lambda-events/src/custom_serde/headers.rs | 189 ++++ lambda-events/src/custom_serde/http_method.rs | 103 ++ lambda-events/src/custom_serde/mod.rs | 455 +++++++++ lambda-events/src/encodings.rs | 449 +++++++++ lambda-events/src/event/activemq/mod.rs | 66 ++ lambda-events/src/event/alb/mod.rs | 98 ++ lambda-events/src/event/apigw/mod.rs | 911 ++++++++++++++++++ lambda-events/src/event/appsync/mod.rs | 165 ++++ lambda-events/src/event/autoscaling/mod.rs | 111 +++ lambda-events/src/event/chime_bot/mod.rs | 52 + lambda-events/src/event/clientvpn/mod.rs | 61 ++ .../src/event/cloudwatch_events/cloudtrail.rs | 49 + .../src/event/cloudwatch_events/codedeploy.rs | 35 + .../event/cloudwatch_events/codepipeline.rs | 47 + .../src/event/cloudwatch_events/ec2.rs | 10 + .../src/event/cloudwatch_events/emr.rs | 50 + .../src/event/cloudwatch_events/gamelift.rs | 119 +++ .../src/event/cloudwatch_events/glue.rs | 89 ++ .../src/event/cloudwatch_events/health.rs | 31 + .../src/event/cloudwatch_events/kms.rs | 9 + .../src/event/cloudwatch_events/macie.rs | 223 +++++ .../src/event/cloudwatch_events/mod.rs | 50 + .../src/event/cloudwatch_events/opsworks.rs | 55 ++ .../src/event/cloudwatch_events/signin.rs | 50 + .../src/event/cloudwatch_events/sms.rs | 15 + .../src/event/cloudwatch_events/ssm.rs | 240 +++++ .../src/event/cloudwatch_events/tag.rs | 16 + .../event/cloudwatch_events/trustedadvisor.rs | 17 + .../src/event/cloudwatch_logs/mod.rs | 157 +++ lambda-events/src/event/code_commit/mod.rs | 82 ++ lambda-events/src/event/codebuild/mod.rs | 237 +++++ lambda-events/src/event/codedeploy/mod.rs | 92 ++ .../src/event/codepipeline_cloudwatch/mod.rs | 114 +++ .../src/event/codepipeline_job/mod.rs | 129 +++ lambda-events/src/event/cognito/mod.rs | 612 ++++++++++++ lambda-events/src/event/config/mod.rs | 52 + lambda-events/src/event/connect/mod.rs | 116 +++ .../src/event/dynamodb/attributes.rs | 191 ++++ lambda-events/src/event/dynamodb/mod.rs | 280 ++++++ lambda-events/src/event/ecr_scan/mod.rs | 73 ++ lambda-events/src/event/firehose/mod.rs | 87 ++ lambda-events/src/event/iam/mod.rs | 23 + lambda-events/src/event/iot/mod.rs | 99 ++ lambda-events/src/event/iot_1_click/mod.rs | 72 ++ lambda-events/src/event/iot_button/mod.rs | 27 + lambda-events/src/event/iot_deprecated/mod.rs | 35 + lambda-events/src/event/kafka/mod.rs | 49 + lambda-events/src/event/kinesis/analytics.rs | 35 + lambda-events/src/event/kinesis/event.rs | 88 ++ lambda-events/src/event/kinesis/mod.rs | 3 + .../src/event/lambda_function_urls/mod.rs | 104 ++ lambda-events/src/event/lex/mod.rs | 130 +++ lambda-events/src/event/mod.rs | 140 +++ lambda-events/src/event/rabbitmq/mod.rs | 76 ++ lambda-events/src/event/s3/batch_job.rs | 58 ++ lambda-events/src/event/s3/event.rs | 114 +++ lambda-events/src/event/s3/mod.rs | 5 + lambda-events/src/event/s3/object_lambda.rs | 171 ++++ lambda-events/src/event/ses/mod.rs | 155 +++ lambda-events/src/event/sns/mod.rs | 248 +++++ lambda-events/src/event/sqs/mod.rs | 161 ++++ lambda-events/src/event/streams/mod.rs | 44 + .../src/fixtures/example-activemq-event.json | 25 + ...lb-lambda-target-request-headers-only.json | 26 + ...bda-target-request-multivalue-headers.json | 49 + .../example-alb-lambda-target-response.json | 15 + .../example-apigw-console-test-request.json | 45 + ...pigw-custom-auth-request-type-request.json | 88 ++ .../example-apigw-custom-auth-request.json | 5 + .../example-apigw-custom-auth-response.json | 19 + .../src/fixtures/example-apigw-request.json | 96 ++ .../src/fixtures/example-apigw-response.json | 42 + ...example-apigw-restapi-openapi-request.json | 97 ++ ...apigw-v2-custom-authorizer-v1-request.json | 61 ++ ...authorizer-v2-request-without-cookies.json | 49 + ...apigw-v2-custom-authorizer-v2-request.json | 51 + ...2-custom-authorizer-websocket-request.json | 51 + .../example-apigw-v2-request-iam.json | 73 ++ ...ample-apigw-v2-request-jwt-authorizer.json | 70 ++ ...le-apigw-v2-request-lambda-authorizer.json | 63 ++ ...xample-apigw-v2-request-no-authorizer.json | 48 + ...pigw-websocket-request-without-method.json | 58 ++ .../example-apigw-websocket-request.json | 108 +++ .../fixtures/example-appsync-batchinvoke.json | 28 + .../example-appsync-identity-cognito.json | 19 + .../example-appsync-identity-iam.json | 11 + .../src/fixtures/example-appsync-invoke.json | 14 + .../example-appsync-lambda-auth-request.json | 12 + .../example-appsync-lambda-auth-response.json | 8 + ...e-autoscaling-event-launch-successful.json | 29 + ...autoscaling-event-launch-unsuccessful.json | 28 + ...le-autoscaling-event-lifecycle-action.json | 21 + ...le-autoscaling-event-terminate-action.json | 19 + ...utoscaling-event-terminate-successful.json | 29 + ...oscaling-event-terminate-unsuccessful.json | 28 + ...e-clientvpn-connectionhandler-request.json | 12 + ...ch-alarm-sns-payload-multiple-metrics.json | 40 + ...watch-alarm-sns-payload-single-metric.json | 22 + .../example-cloudwatch_logs-event.json | 6 + .../fixtures/example-code_commit-event.json | 27 + .../example-codebuild-phase-change.json | 138 +++ .../example-codebuild-state-change.json | 142 +++ .../example-codedeploy-deployment-event.json | 22 + .../example-codedeploy-instance-event.json | 23 + ...e-action-execution-stage-change-event.json | 27 + ...pipeline-execution-stage-change-event.json | 18 + ...pipeline-execution-state-change-event.json | 18 + .../example-codepipeline_job-event.json | 34 + ...event-userpools-create-auth-challenge.json | 40 + ...cognito-event-userpools-custommessage.json | 28 + ...th-challenge-optional-response-fields.json | 34 + ...event-userpools-define-auth-challenge.json | 36 + ...e-cognito-event-userpools-migrateuser.json | 34 + ...to-event-userpools-postauthentication.json | 22 + ...nito-event-userpools-postconfirmation.json | 21 + ...ito-event-userpools-preauthentication.json | 21 + ...ple-cognito-event-userpools-presignup.json | 29 + ...-event-userpools-pretokengen-incoming.json | 29 + ...e-cognito-event-userpools-pretokengen.json | 40 + ...uth-challenge-optional-answer-correct.json | 29 + ...event-userpools-verify-auth-challenge.json | 30 + .../src/fixtures/example-cognito-event.json | 17 + .../src/fixtures/example-config-event.json | 13 + .../example-connect-event-without-queue.json | 29 + .../src/fixtures/example-connect-event.json | 33 + ...odb-event-record-with-optional-fields.json | 26 + .../src/fixtures/example-dynamodb-event.json | 153 +++ .../example-ecr-image-scan-event.json | 24 + .../src/fixtures/example-firehose-event.json | 33 + .../example-iot-custom-auth-request.json | 25 + .../example-iot-custom-auth-response.json | 19 + .../fixtures/example-iot_1_click-event.json | 30 + .../fixtures/example-iot_button-event.json | 5 + .../src/fixtures/example-kafka-event.json | 24 + .../src/fixtures/example-kinesis-event.json | 36 + .../example-kinesis-firehose-event.json | 33 + .../example-kinesis-firehose-response.json | 32 + .../src/fixtures/example-lex-event.json | 44 + .../src/fixtures/example-lex-response.json | 40 + .../src/fixtures/example-rabbitmq-event.json | 52 + .../example-s3-event-with-decoded.json | 41 + .../src/fixtures/example-s3-event.json | 40 + ...-lambda-event-get-object-assumed-role.json | 42 + ...s3-object-lambda-event-get-object-iam.json | 29 + ...3-object-lambda-event-head-object-iam.json | 28 + ...-object-lambda-event-list-objects-iam.json | 28 + ...ject-lambda-event-list-objects-v2-iam.json | 28 + .../src/fixtures/example-ses-event.json | 101 ++ .../fixtures/example-ses-lambda-event.json | 101 ++ .../src/fixtures/example-ses-s3-event.json | 115 +++ .../src/fixtures/example-ses-sns-event.json | 113 +++ .../src/fixtures/example-sns-event-obj.json | 22 + .../example-sns-event-pascal-case.json | 22 + .../src/fixtures/example-sns-event.json | 22 + .../fixtures/example-sqs-batch-response.json | 10 + .../src/fixtures/example-sqs-event-obj.json | 41 + .../src/fixtures/example-sqs-event.json | 41 + lambda-events/src/lib.rs | 175 ++++ lambda-events/src/time_window.rs | 81 ++ lambda-http/Cargo.toml | 3 +- lambda-http/src/request.rs | 4 +- lambda-http/src/response.rs | 6 +- 178 files changed, 12467 insertions(+), 104 deletions(-) create mode 100644 .github/actions/rust-build/action.yml create mode 100644 .github/workflows/build-events.yml create mode 100644 .github/workflows/build-extension.yml create mode 100644 .github/workflows/build-runtime.yml delete mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/check-examples.yml create mode 100644 .github/workflows/format.yml create mode 100644 lambda-events/Cargo.toml create mode 100644 lambda-events/README.md create mode 100644 lambda-events/src/custom_serde/codebuild_time.rs create mode 100644 lambda-events/src/custom_serde/float_unix_epoch.rs create mode 100644 lambda-events/src/custom_serde/headers.rs create mode 100644 lambda-events/src/custom_serde/http_method.rs create mode 100644 lambda-events/src/custom_serde/mod.rs create mode 100644 lambda-events/src/encodings.rs create mode 100644 lambda-events/src/event/activemq/mod.rs create mode 100644 lambda-events/src/event/alb/mod.rs create mode 100644 lambda-events/src/event/apigw/mod.rs create mode 100644 lambda-events/src/event/appsync/mod.rs create mode 100644 lambda-events/src/event/autoscaling/mod.rs create mode 100644 lambda-events/src/event/chime_bot/mod.rs create mode 100644 lambda-events/src/event/clientvpn/mod.rs create mode 100644 lambda-events/src/event/cloudwatch_events/cloudtrail.rs create mode 100644 lambda-events/src/event/cloudwatch_events/codedeploy.rs create mode 100644 lambda-events/src/event/cloudwatch_events/codepipeline.rs create mode 100644 lambda-events/src/event/cloudwatch_events/ec2.rs create mode 100644 lambda-events/src/event/cloudwatch_events/emr.rs create mode 100644 lambda-events/src/event/cloudwatch_events/gamelift.rs create mode 100644 lambda-events/src/event/cloudwatch_events/glue.rs create mode 100644 lambda-events/src/event/cloudwatch_events/health.rs create mode 100644 lambda-events/src/event/cloudwatch_events/kms.rs create mode 100644 lambda-events/src/event/cloudwatch_events/macie.rs create mode 100644 lambda-events/src/event/cloudwatch_events/mod.rs create mode 100644 lambda-events/src/event/cloudwatch_events/opsworks.rs create mode 100644 lambda-events/src/event/cloudwatch_events/signin.rs create mode 100644 lambda-events/src/event/cloudwatch_events/sms.rs create mode 100644 lambda-events/src/event/cloudwatch_events/ssm.rs create mode 100644 lambda-events/src/event/cloudwatch_events/tag.rs create mode 100644 lambda-events/src/event/cloudwatch_events/trustedadvisor.rs create mode 100644 lambda-events/src/event/cloudwatch_logs/mod.rs create mode 100644 lambda-events/src/event/code_commit/mod.rs create mode 100644 lambda-events/src/event/codebuild/mod.rs create mode 100644 lambda-events/src/event/codedeploy/mod.rs create mode 100644 lambda-events/src/event/codepipeline_cloudwatch/mod.rs create mode 100644 lambda-events/src/event/codepipeline_job/mod.rs create mode 100644 lambda-events/src/event/cognito/mod.rs create mode 100644 lambda-events/src/event/config/mod.rs create mode 100644 lambda-events/src/event/connect/mod.rs create mode 100644 lambda-events/src/event/dynamodb/attributes.rs create mode 100644 lambda-events/src/event/dynamodb/mod.rs create mode 100644 lambda-events/src/event/ecr_scan/mod.rs create mode 100644 lambda-events/src/event/firehose/mod.rs create mode 100644 lambda-events/src/event/iam/mod.rs create mode 100644 lambda-events/src/event/iot/mod.rs create mode 100644 lambda-events/src/event/iot_1_click/mod.rs create mode 100644 lambda-events/src/event/iot_button/mod.rs create mode 100644 lambda-events/src/event/iot_deprecated/mod.rs create mode 100644 lambda-events/src/event/kafka/mod.rs create mode 100644 lambda-events/src/event/kinesis/analytics.rs create mode 100644 lambda-events/src/event/kinesis/event.rs create mode 100644 lambda-events/src/event/kinesis/mod.rs create mode 100644 lambda-events/src/event/lambda_function_urls/mod.rs create mode 100644 lambda-events/src/event/lex/mod.rs create mode 100644 lambda-events/src/event/mod.rs create mode 100644 lambda-events/src/event/rabbitmq/mod.rs create mode 100644 lambda-events/src/event/s3/batch_job.rs create mode 100644 lambda-events/src/event/s3/event.rs create mode 100644 lambda-events/src/event/s3/mod.rs create mode 100644 lambda-events/src/event/s3/object_lambda.rs create mode 100644 lambda-events/src/event/ses/mod.rs create mode 100644 lambda-events/src/event/sns/mod.rs create mode 100644 lambda-events/src/event/sqs/mod.rs create mode 100644 lambda-events/src/event/streams/mod.rs create mode 100644 lambda-events/src/fixtures/example-activemq-event.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json create mode 100644 lambda-events/src/fixtures/example-alb-lambda-target-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-console-test-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-custom-auth-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-response.json create mode 100644 lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-iam.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json create mode 100644 lambda-events/src/fixtures/example-apigw-websocket-request.json create mode 100644 lambda-events/src/fixtures/example-appsync-batchinvoke.json create mode 100644 lambda-events/src/fixtures/example-appsync-identity-cognito.json create mode 100644 lambda-events/src/fixtures/example-appsync-identity-iam.json create mode 100644 lambda-events/src/fixtures/example-appsync-invoke.json create mode 100644 lambda-events/src/fixtures/example-appsync-lambda-auth-request.json create mode 100644 lambda-events/src/fixtures/example-appsync-lambda-auth-response.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json create mode 100644 lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json create mode 100644 lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json create mode 100644 lambda-events/src/fixtures/example-cloudwatch_logs-event.json create mode 100644 lambda-events/src/fixtures/example-code_commit-event.json create mode 100644 lambda-events/src/fixtures/example-codebuild-phase-change.json create mode 100644 lambda-events/src/fixtures/example-codebuild-state-change.json create mode 100644 lambda-events/src/fixtures/example-codedeploy-deployment-event.json create mode 100644 lambda-events/src/fixtures/example-codedeploy-instance-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json create mode 100644 lambda-events/src/fixtures/example-codepipeline_job-event.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json create mode 100644 lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json create mode 100644 lambda-events/src/fixtures/example-cognito-event.json create mode 100644 lambda-events/src/fixtures/example-config-event.json create mode 100644 lambda-events/src/fixtures/example-connect-event-without-queue.json create mode 100644 lambda-events/src/fixtures/example-connect-event.json create mode 100644 lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json create mode 100644 lambda-events/src/fixtures/example-dynamodb-event.json create mode 100644 lambda-events/src/fixtures/example-ecr-image-scan-event.json create mode 100644 lambda-events/src/fixtures/example-firehose-event.json create mode 100644 lambda-events/src/fixtures/example-iot-custom-auth-request.json create mode 100644 lambda-events/src/fixtures/example-iot-custom-auth-response.json create mode 100644 lambda-events/src/fixtures/example-iot_1_click-event.json create mode 100644 lambda-events/src/fixtures/example-iot_button-event.json create mode 100644 lambda-events/src/fixtures/example-kafka-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-firehose-event.json create mode 100644 lambda-events/src/fixtures/example-kinesis-firehose-response.json create mode 100644 lambda-events/src/fixtures/example-lex-event.json create mode 100644 lambda-events/src/fixtures/example-lex-response.json create mode 100644 lambda-events/src/fixtures/example-rabbitmq-event.json create mode 100644 lambda-events/src/fixtures/example-s3-event-with-decoded.json create mode 100644 lambda-events/src/fixtures/example-s3-event.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json create mode 100644 lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json create mode 100644 lambda-events/src/fixtures/example-ses-event.json create mode 100644 lambda-events/src/fixtures/example-ses-lambda-event.json create mode 100644 lambda-events/src/fixtures/example-ses-s3-event.json create mode 100644 lambda-events/src/fixtures/example-ses-sns-event.json create mode 100644 lambda-events/src/fixtures/example-sns-event-obj.json create mode 100644 lambda-events/src/fixtures/example-sns-event-pascal-case.json create mode 100644 lambda-events/src/fixtures/example-sns-event.json create mode 100644 lambda-events/src/fixtures/example-sqs-batch-response.json create mode 100644 lambda-events/src/fixtures/example-sqs-event-obj.json create mode 100644 lambda-events/src/fixtures/example-sqs-event.json create mode 100644 lambda-events/src/lib.rs create mode 100644 lambda-events/src/time_window.rs diff --git a/.github/actions/rust-build/action.yml b/.github/actions/rust-build/action.yml new file mode 100644 index 00000000..85b5c0e8 --- /dev/null +++ b/.github/actions/rust-build/action.yml @@ -0,0 +1,26 @@ +name: "Rust builds" +description: "Builds, tests, and formats Rust code" +inputs: + package: + required: true + description: "the Rust package to test" + toolchain: + required: true + description: "the Rust toolchain to use" + +runs: + using: "composite" + steps: + - uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ inputs.toolchain }} + components: clippy, rustfmt + - uses: Swatinem/rust-cache@v2 + + - name: Build + shell: bash + run: cargo build --all-features --verbose --package ${{ inputs.package }} + + - name: Run tests + shell: bash + run: cargo test --all-features --verbose --package ${{ inputs.package }} diff --git a/.github/workflows/build-events.yml b/.github/workflows/build-events.yml new file mode 100644 index 00000000..dbf9a0ae --- /dev/null +++ b/.github/workflows/build-events.yml @@ -0,0 +1,28 @@ +name: Check Lambda Events + +on: + push: + paths: + - 'lambda-events/**' + pull_request: + paths: + - 'lambda-events/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + RUST_BACKTRACE: 1 + steps: + - uses: actions/checkout@v3 + + - name: Build events + uses: ./.github/actions/rust-build + with: + package: aws_lambda_events + toolchain: ${{ matrix.toolchain}} diff --git a/.github/workflows/build-extension.yml b/.github/workflows/build-extension.yml new file mode 100644 index 00000000..0905f289 --- /dev/null +++ b/.github/workflows/build-extension.yml @@ -0,0 +1,39 @@ +name: Check Lambda Runtime + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-extension/**' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-extension/**' + + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + 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}} diff --git a/.github/workflows/build-runtime.yml b/.github/workflows/build-runtime.yml new file mode 100644 index 00000000..68913c95 --- /dev/null +++ b/.github/workflows/build-runtime.yml @@ -0,0 +1,45 @@ +name: Check Lambda Runtime + +on: + push: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + + pull_request: + paths: + - 'lambda-runtime-api-client/**' + - 'lambda-runtime/**' + - 'lambda-http/**' + +jobs: + build-runtime: + runs-on: ubuntu-latest + strategy: + matrix: + toolchain: + - "1.62.0" # Current MSRV + - stable + env: + 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}} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 370d8249..00000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,93 +0,0 @@ -name: Rust - -on: [push, pull_request, workflow_dispatch] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macOS-latest - toolchain: - - "1.62.0" # Current MSRV - - stable - - beta - - nightly - target: - - "" - - x86_64-unknown-linux-musl - include: - - rust: nightly - allow_failure: true - env: - RUST_BACKTRACE: 1 - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: ${{ matrix.toolchain }} - - uses: Swatinem/rust-cache@v2 - - - name: Build - run: cargo build --all --verbose - env: - TARGET: ${{ matrix.target }} - continue-on-error: ${{ matrix.allow_failure }} - - name: Run tests - run: cargo test --all --verbose - env: - TARGET: ${{ matrix.target }} - continue-on-error: ${{ matrix.allow_failure }} - formatting: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Run fmt check - run: cargo fmt --all -- --check - - name: Run clippy check - run: cargo clippy --all-features -- -D warnings - - check-examples: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Check examples - working-directory: examples - shell: bash - run: ./check-examples.sh - - # publish rustdoc to a gh-pages branch on pushes to main - # this can be helpful to those depending on the mainline branch - publish-docs: - if: github.ref == 'refs/heads/main' - runs-on: ubuntu-latest - needs: [build] - steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@master - with: - toolchain: stable - - uses: Swatinem/rust-cache@v2 - - - name: Generate Docs - run: | - cargo doc --no-deps - echo "" > target/doc/index.html - - name: Publish - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./target/doc diff --git a/.github/workflows/check-examples.yml b/.github/workflows/check-examples.yml new file mode 100644 index 00000000..ba7bc709 --- /dev/null +++ b/.github/workflows/check-examples.yml @@ -0,0 +1,16 @@ +name: Check examples + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + + - name: Check examples + working-directory: examples + shell: bash + run: ./check-examples.sh \ No newline at end of file diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 00000000..f18d1f83 --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,55 @@ +name: Formatting and Linting + +on: [push, pull_request] + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: dtolnay/rust-toolchain@stable + - 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'); + + - 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/Cargo.toml b/Cargo.toml index 7361557e..48bcd5db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,8 @@ members = [ "lambda-integration-tests", "lambda-runtime-api-client", "lambda-runtime", - "lambda-extension" + "lambda-extension", + "lambda-events" ] exclude = ["examples"] diff --git a/README.md b/README.md index 8fdd232c..157c1c98 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ This package makes it easy to run AWS Lambda Functions written in Rust. This wor - [![Docs](https://docs.rs/lambda_runtime/badge.svg)](https://docs.rs/lambda_runtime) **`lambda-runtime`** is a library that provides a Lambda runtime for applications written in Rust. - [![Docs](https://docs.rs/lambda_http/badge.svg)](https://docs.rs/lambda_http) **`lambda-http`** is a library that makes it easy to write API Gateway proxy event focused Lambda functions in Rust. - [![Docs](https://docs.rs/lambda-extension/badge.svg)](https://docs.rs/lambda-extension) **`lambda-extension`** is a library that makes it easy to write Lambda Runtime Extensions in Rust. +- [![Docs](https://docs.rs/aws_lambda_events/badge.svg)](https://docs.rs/aws_lambda_events) **`lambda-events`** is a library with strongly-typed Lambda event structs in Rust. - [![Docs](https://docs.rs/lambda_runtime_api_client/badge.svg)](https://docs.rs/lambda_runtime_api_client) **`lambda-runtime-api-client`** is a shared library between the lambda runtime and lambda extension libraries that includes a common API client to talk with the AWS Lambda Runtime API. The Rust runtime client is an experimental package. It is subject to change and intended only for evaluation purposes. diff --git a/examples/advanced-sqs-partial-batch-failures/Cargo.toml b/examples/advanced-sqs-partial-batch-failures/Cargo.toml index 06e56053..0dfe49d9 100644 --- a/examples/advanced-sqs-partial-batch-failures/Cargo.toml +++ b/examples/advanced-sqs-partial-batch-failures/Cargo.toml @@ -8,8 +8,8 @@ serde = "^1" serde_derive = "^1" serde_with = { version = "^2", features = ["json"], optional = true } serde_json = "^1" -aws_lambda_events = "0.7.3" -lambda_runtime = "0.7" +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"] } diff --git a/examples/basic-s3-thumbnail/Cargo.toml b/examples/basic-s3-thumbnail/Cargo.toml index dfa6d69b..0ef0d463 100644 --- a/examples/basic-s3-thumbnail/Cargo.toml +++ b/examples/basic-s3-thumbnail/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws_lambda_events = "0.7.2" +aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1" tokio = { version = "1", features = ["macros"] } diff --git a/examples/basic-sqs/Cargo.toml b/examples/basic-sqs/Cargo.toml index 086a22dc..a1b11567 100644 --- a/examples/basic-sqs/Cargo.toml +++ b/examples/basic-sqs/Cargo.toml @@ -15,7 +15,7 @@ edition = "2021" # and it will keep the alphabetic ordering for you. [dependencies] -aws_lambda_events = "0.7.2" +aws_lambda_events = { path = "../../lambda-events" } lambda_runtime = { path = "../../lambda-runtime" } serde = "1.0.136" tokio = { version = "1", features = ["macros"] } diff --git a/lambda-events/Cargo.toml b/lambda-events/Cargo.toml new file mode 100644 index 00000000..225d9d8f --- /dev/null +++ b/lambda-events/Cargo.toml @@ -0,0 +1,117 @@ +[package] +name = "aws_lambda_events" +version = "0.9.0" +description = "AWS Lambda event definitions" +authors = [ + "Christian Legnitto ", + "Sam Rijs ", + "David Calavera ", +] +license = "MIT" +homepage = "https://github.com/awslabs/aws-lambda-rust-runtime" +repository = "https://github.com/awslabs/aws-lambda-rust-runtime" +readme = "README.md" +keywords = ["lambda", "aws", "amazon", "events", "S3"] +categories = ["api-bindings", "encoding", "web-programming"] + +[dependencies] +base64 = "0.13" +http = "0.2" +http-body = "0.4" +http-serde = "^1" +serde = "^1" +serde_derive = "^1" +serde_with = { version = "^2", features = ["json"], optional = true } +serde_json = "^1" +serde_dynamo = { version = "^4.1", optional = true } +bytes = { version = "1", features = ["serde"] } +chrono = { version = "0.4.23", default-features = false, features = [ + "clock", + "serde", + "std", +] } +query_map = { version = "^0.6", features = ["serde", "url-query"] } +flate2 = { version = "1.0.24", optional = true } + +[dev-dependencies] +pretty_assertions = "1.3" + +[features] +default = [ + "activemq", + "alb", + "apigw", + "appsync", + "autoscaling", + "chime_bot", + "clientvpn", + "cloudwatch_events", + "cloudwatch_logs", + "code_commit", + "codebuild", + "codedeploy", + "codepipeline_cloudwatch", + "codepipeline_job", + "cognito", + "config", + "connect", + "dynamodb", + "ecr_scan", + "firehose", + "iam", + "iot", + "iot_1_click", + "iot_button", + "iot_deprecated", + "kafka", + "kinesis", + "kinesis_analytics", + "lambda_function_urls", + "lex", + "rabbitmq", + "s3", + "s3_batch_job", + "ses", + "sns", + "sqs", + "streams", +] + +activemq = [] +alb = [] +apigw = [] +appsync = [] +autoscaling = [] +chime_bot = [] +clientvpn = [] +cloudwatch_events = [] +cloudwatch_logs = ["flate2"] +code_commit = [] +codebuild = [] +codedeploy = [] +codepipeline = [] +codepipeline_cloudwatch = [] +codepipeline_job = [] +cognito = [] +config = [] +connect = [] +dynamodb = ["streams", "serde_dynamo"] +ecr_scan = [] +firehose = [] +iam = [] +iot = ["iam"] +iot_1_click = [] +iot_button = [] +iot_deprecated = ["iot"] +kafka = [] +kinesis = [] +kinesis_analytics = ["kinesis"] +lambda_function_urls = [] +lex = [] +rabbitmq = [] +s3 = [] +s3_batch_job = ["s3"] +ses = [] +sns = ["serde_with"] +sqs = ["serde_with"] +streams = [] diff --git a/lambda-events/README.md b/lambda-events/README.md new file mode 100644 index 00000000..0813c63a --- /dev/null +++ b/lambda-events/README.md @@ -0,0 +1,34 @@ +# AWS Lambda Events + +[![crates.io][crate-image]][crate-link] +[![Documentation][docs-image]][docs-link] + +This crate provides strongly-typed [AWS Lambda event structs](https://docs.aws.amazon.com/lambda/latest/dg/invoking-lambda-function.html) in Rust. + +## Installation + +Add the dependency with Cargo: `cargo add aws_lambda_events`. + +## Usage + +The crate itself has no AWS Lambda handler logic and instead exists to serialize +and deserialize AWS Lambda events into strongly-typed Rust structs. + +The types +defined in this crate are usually used with handlers / runtimes provided by the [official Rust runtime](https://github.com/awslabs/aws-lambda-rust-runtime). + +For a list of supported AWS Lambda events and services, see [the crate reference documentation](https://docs.rs/aws_lambda_events). + +## Conditional compilation of features + +This crate divides all Lambda Events into features named after the service that the events are generated from. By default all events are enabled when you include this crate as a dependency to your project. If you only want to import specific events from this crate, you can disable the default features, and enable only the events that you need. This will make your project to compile a little bit faster, since rustc doesn't need to compile events that you're not going to use. Here's an example on how to do that: + +``` +cargo add aws_lambda_events --no-default-features --features apigw,alb +``` + +[//]: # 'badges' +[crate-image]: https://img.shields.io/crates/v/aws_lambda_events.svg +[crate-link]: https://crates.io/crates/aws_lambda_events +[docs-image]: https://docs.rs/aws_lambda_events/badge.svg +[docs-link]: https://docs.rs/aws_lambda_events \ No newline at end of file diff --git a/lambda-events/src/custom_serde/codebuild_time.rs b/lambda-events/src/custom_serde/codebuild_time.rs new file mode 100644 index 00000000..c57ccd6a --- /dev/null +++ b/lambda-events/src/custom_serde/codebuild_time.rs @@ -0,0 +1,105 @@ +use chrono::{DateTime, TimeZone, Utc}; +use serde::de::{Deserialize, Deserializer, Error as DeError, Visitor}; +use serde::ser::Serializer; +use std::fmt; + +// Jan 2, 2006 3:04:05 PM +const CODEBUILD_TIME_FORMAT: &str = "%b %e, %Y %l:%M:%S %p"; + +struct TimeVisitor; +impl<'de> Visitor<'de> for TimeVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "valid codebuild time: {}", CODEBUILD_TIME_FORMAT) + } + + fn visit_str(self, val: &str) -> Result { + Utc.datetime_from_str(val, CODEBUILD_TIME_FORMAT) + .map_err(|e| DeError::custom(format!("Parse error {} for {}", e, val))) + } +} + +pub(crate) mod str_time { + use super::*; + + pub(crate) fn deserialize<'de, D>(d: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + d.deserialize_str(TimeVisitor) + } + + pub fn serialize(date: &DateTime, ser: S) -> Result { + let s = format!("{}", date.format(CODEBUILD_TIME_FORMAT)); + ser.serialize_str(&s) + } +} + +pub(crate) mod optional_time { + use super::*; + + pub(crate) fn deserialize<'de, D>(deserializer: D) -> Result>, D::Error> + where + D: Deserializer<'de>, + { + let s: Option = Option::deserialize(deserializer)?; + if let Some(val) = s { + let visitor = TimeVisitor {}; + return visitor.visit_str(&val).map(Some); + } + + Ok(None) + } + + pub fn serialize(date: &Option>, ser: S) -> Result { + if let Some(date) = date { + return str_time::serialize(date, ser); + } + + ser.serialize_none() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + type TestTime = DateTime; + + #[test] + fn test_deserialize_codebuild_time() { + #[derive(Deserialize)] + struct Test { + #[serde(with = "str_time")] + pub date: TestTime, + } + let data = json!({ + "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 decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.date); + } + + #[test] + fn test_deserialize_codebuild_optional_time() { + #[derive(Deserialize)] + struct Test { + #[serde(with = "optional_time")] + pub date: Option, + } + let data = json!({ + "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 decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(Some(expected), decoded.date); + } +} diff --git a/lambda-events/src/custom_serde/float_unix_epoch.rs b/lambda-events/src/custom_serde/float_unix_epoch.rs new file mode 100644 index 00000000..54fc64e4 --- /dev/null +++ b/lambda-events/src/custom_serde/float_unix_epoch.rs @@ -0,0 +1,109 @@ +use serde::{de, ser}; +use std::fmt; + +use chrono::offset::TimeZone; +use chrono::{DateTime, LocalResult, Utc}; + +enum SerdeError { + NonExistent { timestamp: V }, + Ambiguous { timestamp: V, min: D, max: D }, +} + +fn ne_timestamp(ts: T) -> SerdeError { + SerdeError::NonExistent:: { timestamp: ts } +} + +impl fmt::Debug for SerdeError { + 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 { + match *self { + SerdeError::NonExistent { ref timestamp } => { + write!(f, "value is not a legal timestamp: {}", timestamp) + } + SerdeError::Ambiguous { + ref timestamp, + ref min, + ref max, + } => write!( + f, + "value is an ambiguous timestamp: {}, could be either of {}, {}", + timestamp, min, max + ), + } + } +} + +fn serde_from(me: LocalResult, ts: &V) -> Result +where + E: de::Error, + V: fmt::Display, + T: fmt::Display, +{ + match me { + LocalResult::None => Err(E::custom(ne_timestamp(ts))), + LocalResult::Ambiguous(min, max) => Err(E::custom(SerdeError::Ambiguous { + timestamp: ts, + min, + max, + })), + LocalResult::Single(val) => Ok(val), + } +} + +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, +{ + serializer.serialize_i64(dt.timestamp_millis() / 1000) +} + +/// Deserialize a `DateTime` from a float seconds timestamp +pub fn deserialize<'de, D>(d: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + d.deserialize_f64(SecondsFloatTimestampVisitor) +} + +impl<'de> de::Visitor<'de> for SecondsFloatTimestampVisitor { + type Value = DateTime; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a unix timestamp as a float") + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_u64(self, value: u64) -> Result, E> + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value as i64, 0), &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_i64(self, value: i64) -> Result, E> + where + E: de::Error, + { + serde_from(Utc.timestamp_opt(value, 0), &value) + } + + /// Deserialize a timestamp in seconds since the epoch + fn visit_f64(self, value: f64) -> Result, E> + where + E: de::Error, + { + let time_ms = (value.fract() * 1_000_000.).floor() as u32; + let time_s = value.trunc() as i64; + serde_from(Utc.timestamp_opt(time_s, time_ms), &value) + } +} diff --git a/lambda-events/src/custom_serde/headers.rs b/lambda-events/src/custom_serde/headers.rs new file mode 100644 index 00000000..904ccd9b --- /dev/null +++ b/lambda-events/src/custom_serde/headers.rs @@ -0,0 +1,189 @@ +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 std::{borrow::Cow, fmt}; + +/// 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, +{ + let mut map = serializer.serialize_map(Some(headers.keys_len()))?; + 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.serialize_entry(key.as_str(), &map_values)?; + } + map.end() +} + +/// Serialize a http::HeaderMap into a serde str => str map +pub(crate) fn serialize_headers(headers: &HeaderMap, serializer: S) -> Result +where + S: Serializer, +{ + 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)?; + } + map.end() +} + +#[derive(serde::Deserialize)] +#[serde(untagged)] +enum OneOrMore<'a> { + One(Cow<'a, str>), + Strings(Vec>), + Bytes(Vec>), +} + +struct HeaderMapVisitor { + is_human_readable: bool, +} + +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 { + formatter.write_str("lots of things can go wrong with HeaderMap") + } + + fn visit_unit(self) -> Result + where + E: DeError, + { + Ok(HeaderMap::default()) + } + + fn visit_none(self) -> Result + where + E: DeError, + { + Ok(HeaderMap::default()) + } + + 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 = HeaderMap::with_capacity(access.size_hint().unwrap_or(0)); + + if !self.is_human_readable { + 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 { + let val = HeaderValue::from_bytes(&val) + .map_err(|_| de::Error::invalid_value(Unexpected::Bytes(&val), &self))?; + map.append(&key, val); + } + } + } else { + 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 { + OneOrMore::One(val) => { + let val = val + .parse() + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?; + map.insert(key, val); + } + OneOrMore::Strings(arr) => { + for val in arr { + let val = val + .parse() + .map_err(|_| de::Error::invalid_value(Unexpected::Str(&val), &self))?; + map.append(&key, val); + } + } + OneOrMore::Bytes(arr) => { + for val in arr { + let val = HeaderValue::from_bytes(&val) + .map_err(|_| de::Error::invalid_value(Unexpected::Bytes(&val), &self))?; + map.append(&key, val); + } + } + }; + } + } + Ok(map) + } +} + +/// Implementation detail. +pub(crate) fn deserialize_headers<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + let is_human_readable = de.is_human_readable(); + de.deserialize_option(HeaderMapVisitor { is_human_readable }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_deserialize_missing_http_headers() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + pub headers: HeaderMap, + } + let data = json!({ + "not_headers": {} + }); + + let expected = HeaderMap::new(); + + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(expected, decoded.headers); + } + + #[test] + fn test_serialize_headers() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + headers: HeaderMap, + } + let data = json!({ + "headers": { + "Accept": ["*/*"] + } + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(&"*/*", decoded.headers.get("Accept").unwrap()); + + let recoded = serde_json::to_value(decoded).unwrap(); + let decoded: Test = serde_json::from_value(recoded).unwrap(); + assert_eq!(&"*/*", decoded.headers.get("Accept").unwrap()); + } + + #[test] + fn test_null_headers() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_headers")] + headers: HeaderMap, + } + let data = 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 new file mode 100644 index 00000000..6060a429 --- /dev/null +++ b/lambda-events/src/custom_serde/http_method.rs @@ -0,0 +1,103 @@ +use http::Method; +use serde::de::{Deserialize, Deserializer, Error as DeError, Unexpected, Visitor}; +use serde::ser::Serializer; +use std::fmt; + +pub fn serialize(method: &Method, ser: S) -> Result { + ser.serialize_str(method.as_str()) +} + +struct MethodVisitor; +impl<'de> Visitor<'de> for MethodVisitor { + type Value = Method; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + write!(formatter, "valid method name") + } + + fn visit_str(self, val: &str) -> Result { + if val.is_empty() { + Ok(Method::GET) + } else { + val.parse() + .map_err(|_| DeError::invalid_value(Unexpected::Str(val), &self)) + } + } +} + +pub fn deserialize<'de, D>(de: D) -> Result +where + D: Deserializer<'de>, +{ + de.deserialize_str(MethodVisitor) +} + +pub fn deserialize_optional<'de, D>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, +{ + let s: Option = Option::deserialize(deserializer)?; + if let Some(val) = s { + let visitor = MethodVisitor {}; + return visitor.visit_str(&val).map(Some); + } + + Ok(None) +} + +pub fn serialize_optional(method: &Option, ser: S) -> Result { + if let Some(method) = method { + return serialize(method, ser); + } + + ser.serialize_none() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_http_method_serializer() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(with = "crate::custom_serde::http_method")] + pub method: http::Method, + } + let data = json!({ + "method": "DELETE" + }); + let decoded: Test = serde_json::from_value(data.clone()).unwrap(); + assert_eq!(http::Method::DELETE, decoded.method); + + let recoded = serde_json::to_value(decoded).unwrap(); + assert_eq!(data, recoded); + } + + #[test] + fn test_http_optional_method_serializer() { + #[derive(Deserialize, Serialize)] + struct Test { + #[serde(deserialize_with = "deserialize_optional")] + #[serde(serialize_with = "serialize_optional")] + #[serde(default)] + pub method: Option, + } + let data = json!({ + "method": "DELETE" + }); + let decoded: Test = serde_json::from_value(data.clone()).unwrap(); + assert_eq!(Some(http::Method::DELETE), decoded.method); + + let recoded = serde_json::to_value(decoded).unwrap(); + assert_eq!(data, recoded); + + let data = json!({ "method": null }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(None, decoded.method); + + let data = 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 new file mode 100644 index 00000000..964f460e --- /dev/null +++ b/lambda-events/src/custom_serde/mod.rs @@ -0,0 +1,455 @@ +#[allow(unused)] +use base64::{decode, encode}; +use chrono::{DateTime, Duration, TimeZone, Utc}; +use serde; +use serde::de::{Deserialize, Deserializer, Error as DeError}; +use serde::ser::Serializer; +use std::collections::HashMap; + +#[cfg(feature = "codebuild")] +pub(crate) mod codebuild_time; +#[cfg(feature = "codebuild")] +pub type CodeBuildNumber = f32; + +#[cfg(any( + feature = "alb", + feature = "apigw", + feature = "s3", + feature = "iot", + feature = "lambda_function_urls" +))] +mod headers; +#[cfg(any( + feature = "alb", + feature = "apigw", + feature = "s3", + feature = "iot", + feature = "lambda_function_urls" +))] +pub(crate) use self::headers::*; + +#[cfg(feature = "dynamodb")] +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>, +{ + let s: String = String::deserialize(deserializer)?; + decode(s).map_err(DeError::custom) +} + +pub(crate) fn serialize_base64(value: &[u8], serializer: S) -> Result +where + S: Serializer, +{ + serializer.serialize_str(&encode(value)) +} + +/// Deserializes `HashMap<_>`, mapping JSON `null` to an empty map. +pub(crate) fn deserialize_lambda_map<'de, D, K, V>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + K: serde::Deserialize<'de>, + K: std::hash::Hash, + K: std::cmp::Eq, + V: serde::Deserialize<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + +#[cfg(feature = "dynamodb")] +/// Deserializes `Item`, mapping JSON `null` to an empty item. +pub(crate) fn deserialize_lambda_dynamodb_item<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + 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", + test +))] +pub(crate) fn deserialize_nullish_boolean<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + // https://github.com/serde-rs/serde/issues/1098 + let opt = Option::deserialize(deserializer)?; + Ok(opt.unwrap_or_default()) +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + use serde_json; + + #[test] + fn test_deserialize_base64() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_base64")] + v: Vec, + } + let data = json!({ + "v": "SGVsbG8gV29ybGQ=", + }); + let decoded: Test = serde_json::from_value(data).unwrap(); + assert_eq!(String::from_utf8(decoded.v).unwrap(), "Hello World".to_string()); + } + + #[test] + fn test_serialize_base64() { + #[derive(Serialize)] + struct Test { + #[serde(serialize_with = "serialize_base64")] + v: Vec, + } + let instance = Test { + v: "Hello World".as_bytes().to_vec(), + }; + let encoded = serde_json::to_string(&instance).unwrap(); + 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)] + struct Test { + #[serde(deserialize_with = "deserialize_lambda_map")] + v: HashMap, + } + let input = json!({ + "v": {}, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(HashMap::new(), decoded.v); + + let input = json!({ + "v": null, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(HashMap::new(), decoded.v); + } + + #[cfg(feature = "dynamodb")] + #[test] + fn test_deserialize_lambda_dynamodb_item() { + #[derive(Deserialize)] + struct Test { + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + v: serde_dynamo::Item, + } + let input = json!({ + "v": {}, + }); + let decoded: Test = serde_json::from_value(input).unwrap(); + assert_eq!(serde_dynamo::Item::from(HashMap::new()), decoded.v); + + let input = 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)] + struct Test { + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + v: bool, + } + + let test = r#"{"v": null}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + + let test = r#"{}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + + let test = r#"{"v": true}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(true, decoded.v); + + let test = r#"{"v": false}"#; + let decoded: Test = serde_json::from_str(test).unwrap(); + assert_eq!(false, decoded.v); + } +} diff --git a/lambda-events/src/encodings.rs b/lambda-events/src/encodings.rs new file mode 100644 index 00000000..ecd32340 --- /dev/null +++ b/lambda-events/src/encodings.rs @@ -0,0 +1,449 @@ +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 + } +} + +/// Representation of http request and response bodies as supported +/// by API Gateway and ALBs. +/// +/// These come in three flavors +/// * `Empty` ( no body ) +/// * `Text` ( text data ) +/// * `Binary` ( binary data ) +/// +/// Body types can be `Deref` and `AsRef`'d into `[u8]` types much like the [hyper crate](https://crates.io/crates/hyper) +/// +/// # Examples +/// +/// Body types are inferred with `From` implementations. +/// +/// ## Text +/// +/// Types like `String`, `str` whose type reflects +/// text produce `Body::Text` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from("text") { +/// aws_lambda_events::encodings::Body::Text(_) => true, +/// _ => false +/// }) +/// ``` +/// +/// ## Binary +/// +/// Types like `Vec` and `&[u8]` whose types reflect raw bytes produce `Body::Binary` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from("text".as_bytes()) { +/// aws_lambda_events::encodings::Body::Binary(_) => true, +/// _ => false +/// }) +/// ``` +/// +/// `Binary` responses bodies will automatically get based64 encoded to meet API Gateway's response expectations. +/// +/// ## Empty +/// +/// The unit type ( `()` ) whose type represents an empty value produces `Body::Empty` variants +/// +/// ``` +/// assert!(match aws_lambda_events::encodings::Body::from(()) { +/// aws_lambda_events::encodings::Body::Empty => true, +/// _ => false +/// }) +/// ``` +/// +/// +/// 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). +#[derive(Debug, Default, Eq, PartialEq)] +pub enum Body { + /// An empty body + #[default] + Empty, + /// A body containing string data + Text(String), + /// A body containing binary data + Binary(Vec), +} + +impl Body { + /// Decodes body, if needed. + /// + /// # Panics + /// + /// Panics when aws communicates to handler that request is base64 encoded but + /// it can not be base64 decoded + pub fn from_maybe_encoded(is_base64_encoded: bool, body: &str) -> Body { + if is_base64_encoded { + Body::from(::base64::decode(body).expect("failed to decode aws base64 encoded body")) + } else { + Body::from(body) + } + } +} + +impl From<()> for Body { + fn from(_: ()) -> Self { + Body::Empty + } +} + +impl<'a> From<&'a str> for Body { + fn from(s: &'a str) -> Self { + Body::Text(s.into()) + } +} + +impl From for Body { + fn from(b: String) -> Self { + Body::Text(b) + } +} + +impl From> for Body { + #[inline] + fn from(cow: Cow<'static, str>) -> Body { + match cow { + Cow::Borrowed(b) => Body::from(b.to_owned()), + Cow::Owned(o) => Body::from(o), + } + } +} + +impl From> for Body { + #[inline] + fn from(cow: Cow<'static, [u8]>) -> Body { + match cow { + Cow::Borrowed(b) => Body::from(b), + Cow::Owned(o) => Body::from(o), + } + } +} + +impl From> for Body { + fn from(b: Vec) -> Self { + Body::Binary(b) + } +} + +impl<'a> From<&'a [u8]> for Body { + fn from(b: &'a [u8]) -> Self { + Body::Binary(b.to_vec()) + } +} + +impl Deref for Body { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_ref() + } +} + +impl AsRef<[u8]> for Body { + #[inline] + fn as_ref(&self) -> &[u8] { + match self { + Body::Empty => &[], + Body::Text(ref bytes) => bytes.as_ref(), + Body::Binary(ref bytes) => bytes.as_ref(), + } + } +} + +impl Clone for Body { + fn clone(&self) -> Self { + match self { + Body::Empty => Body::Empty, + Body::Text(ref bytes) => Body::Text(bytes.clone()), + Body::Binary(ref bytes) => Body::Binary(bytes.clone()), + } + } +} + +impl Serialize for Body { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + Body::Text(data) => { + serializer.serialize_str(::std::str::from_utf8(data.as_ref()).map_err(S::Error::custom)?) + } + Body::Binary(data) => serializer.collect_str(&Base64Display::with_config(data, base64::STANDARD)), + Body::Empty => serializer.serialize_unit(), + } + } +} + +impl<'de> Deserialize<'de> for Body { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct BodyVisitor; + + impl<'de> Visitor<'de> for BodyVisitor { + type Value = Body; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("string") + } + + fn visit_str(self, value: &str) -> Result + where + E: DeError, + { + Ok(Body::from(value)) + } + } + + deserializer.deserialize_str(BodyVisitor) + } +} + +impl HttpBody for Body { + type Data = Bytes; + type Error = 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) + } + + fn size_hint(&self) -> SizeHint { + match self { + Body::Empty => SizeHint::default(), + Body::Text(ref s) => SizeHint::with_exact(s.len() as u64), + Body::Binary(ref b) => SizeHint::with_exact(b.len() as u64), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + use std::collections::HashMap; + + #[test] + fn body_has_default() { + assert_eq!(Body::default(), Body::Empty); + } + + #[test] + fn from_unit() { + assert_eq!(Body::from(()), Body::Empty); + } + + #[test] + fn from_str() { + match Body::from(String::from("foo").as_str()) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_string() { + match Body::from(String::from("foo")) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_cow_str() { + match Body::from(Cow::from("foo")) { + Body::Text(_) => (), + not => panic!("expected Body::Text(...) got {:?}", not), + } + } + + #[test] + fn from_cow_bytes() { + match Body::from(Cow::from("foo".as_bytes())) { + Body::Binary(_) => (), + not => panic!("expected Body::Binary(...) got {:?}", not), + } + } + + #[test] + fn from_bytes() { + match Body::from("foo".as_bytes()) { + Body::Binary(_) => (), + not => panic!("expected Body::Binary(...) got {:?}", not), + } + } + + #[test] + fn serialize_text() { + let mut map = HashMap::new(); + map.insert("foo", Body::from("bar")); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"bar"}"#); + } + + #[test] + fn serialize_binary() { + let mut map = HashMap::new(); + map.insert("foo", Body::from("bar".as_bytes())); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":"YmFy"}"#); + } + + #[test] + fn serialize_empty() { + let mut map = HashMap::new(); + map.insert("foo", Body::Empty); + assert_eq!(serde_json::to_string(&map).unwrap(), r#"{"foo":null}"#); + } + + #[test] + fn serialize_from_maybe_encoded() { + match Body::from_maybe_encoded(false, "foo") { + Body::Text(_) => (), + 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), + } + } +} diff --git a/lambda-events/src/event/activemq/mod.rs b/lambda-events/src/event/activemq/mod.rs new file mode 100644 index 00000000..fcb490ec --- /dev/null +++ b/lambda-events/src/event/activemq/mod.rs @@ -0,0 +1,66 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + pub messages: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqMessage { + #[serde(default)] + #[serde(rename = "messageID")] + pub message_id: Option, + #[serde(default)] + pub message_type: Option, + pub timestamp: i64, + pub delivery_mode: i64, + #[serde(default)] + #[serde(rename = "correlationID")] + pub correlation_id: Option, + #[serde(default)] + pub reply_to: Option, + pub destination: ActiveMqDestination, + pub redelivered: bool, + #[serde(default)] + pub type_: Option, + pub expiration: i64, + pub priority: i64, + #[serde(default)] + pub data: Option, + pub broker_in_time: i64, + pub broker_out_time: i64, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub properties: HashMap, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActiveMqDestination { + #[serde(default)] + pub physical_name: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "activemq")] + fn example_activemq_event() { + let data = include_bytes!("../../fixtures/example-activemq-event.json"); + let parsed: ActiveMqEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ActiveMqEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/alb/mod.rs b/lambda-events/src/event/alb/mod.rs new file mode 100644 index 00000000..b9a69ce5 --- /dev/null +++ b/lambda-events/src/event/alb/mod.rs @@ -0,0 +1,98 @@ +use crate::custom_serde::{http_method, serialize_headers, serialize_multi_value_headers}; +use crate::encodings::Body; +use http::{HeaderMap, Method}; +use query_map::QueryMap; + +/// `AlbTargetGroupRequest` contains data originating from the ALB Lambda target group integration +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupRequest { + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub query_string_parameters: QueryMap, + #[serde(default)] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + pub request_context: AlbTargetGroupRequestContext, + pub is_base64_encoded: bool, + pub body: Option, +} + +/// `AlbTargetGroupRequestContext` contains the information to identify the load balancer invoking the lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupRequestContext { + pub elb: ElbContext, +} + +/// `ElbContext` contains the information to identify the ARN invoking the lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ElbContext { + /// nolint: stylecheck + #[serde(default)] + pub target_group_arn: Option, +} + +/// `AlbTargetGroupResponse` configures the response to be returned by the ALB Lambda target group for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlbTargetGroupResponse { + pub status_code: i64, + #[serde(default)] + pub status_description: Option, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_multi_value_headers")] + pub multi_value_headers: HeaderMap, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + pub is_base64_encoded: bool, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_request_headers_only() { + 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: AlbTargetGroupRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_request_multivalue_headers() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-request-multivalue-headers.json"); + let parsed: AlbTargetGroupRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AlbTargetGroupRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "alb")] + fn example_alb_lambda_target_response() { + let data = include_bytes!("../../fixtures/example-alb-lambda-target-response.json"); + let parsed: AlbTargetGroupResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AlbTargetGroupResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/apigw/mod.rs b/lambda-events/src/event/apigw/mod.rs new file mode 100644 index 00000000..80063911 --- /dev/null +++ b/lambda-events/src/event/apigw/mod.rs @@ -0,0 +1,911 @@ +use crate::custom_serde::{ + deserialize_headers, deserialize_lambda_map, 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::de::DeserializeOwned; +use serde::ser::Serialize; +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, + T1: Serialize, +{ + /// The resource path defined in API Gateway + #[serde(default)] + pub resource: Option, + /// The url path for the caller + #[serde(default)] + pub path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[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, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + #[serde(default)] + #[serde(bound = "")] + pub request_context: ApiGatewayProxyRequestContext, + #[serde(default)] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayProxyResponse` configures the response to be returned by API Gateway for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayProxyResponse { + pub status_code: i64, + #[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayProxyRequestContext` contains the information to identify the AWS account and resources invoking the +/// 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, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + pub operation_name: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub identity: ApiGatewayRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub authorizer: HashMap, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + pub request_time: Option, + pub request_time_epoch: i64, + /// The API Gateway rest API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +/// `ApiGatewayV2httpRequest` contains data coming from the new HTTP API Gateway +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequest { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + pub cookies: Option>, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde( + default, + deserialize_with = "query_map::serde::aws_api_gateway_v2::deserialize_empty" + )] + pub query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + pub request_context: ApiGatewayV2httpRequestContext, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub body: Option, + #[serde(default)] + pub is_base64_encoded: bool, +} + +/// `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, +{ + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(bound = "", default)] + pub authorizer: Option>, + /// The API Gateway HTTP API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub time: Option, + pub time_epoch: i64, + pub http: ApiGatewayV2httpRequestContextHttpDescription, + pub authentication: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerDescription` 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 jwt: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub lambda: HashMap, + pub iam: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerJwtDescription` contains JWT authorizer information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerJwtDescription { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub claims: HashMap, + pub scopes: Option>, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerIamDescription { + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub caller_id: Option, + pub cognito_identity: Option, + #[serde(default)] + pub principal_org_id: Option, + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_id: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity` contains Cognito identity information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthorizerCognitoIdentity { + pub amr: Vec, + #[serde(default)] + pub identity_id: Option, + #[serde(default)] + pub identity_pool_id: Option, +} + +/// `ApiGatewayV2httpRequestContextHttpDescription` contains HTTP information for the request context. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextHttpDescription { + #[serde(with = "http_method")] + pub method: Method, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub user_agent: Option, +} + +/// `ApiGatewayV2httpResponse` configures the response to be returned by API Gateway V2 for the request +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpResponse { + pub status_code: i64, + #[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, + #[serde(skip_serializing_if = "Option::is_none")] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, + pub cookies: Vec, +} + +/// `ApiGatewayRequestIdentity` contains identity information for the request caller. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayRequestIdentity { + #[serde(default)] + pub cognito_identity_pool_id: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub cognito_identity_id: Option, + #[serde(default)] + pub caller: Option, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub api_key_id: Option, + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub cognito_authentication_type: Option, + #[serde(default)] + pub cognito_authentication_provider: Option, + /// nolint: stylecheck + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_agent: Option, + #[serde(default)] + pub user: Option, +} + +/// `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, + T1: Serialize, + T2: DeserializeOwned, + T2: Serialize, +{ + /// The resource path defined in API Gateway + #[serde(default)] + pub resource: Option, + /// The url path for the caller + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[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, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + #[serde(bound = "", default)] + pub request_context: ApiGatewayWebsocketProxyRequestContext, + #[serde(default)] + pub body: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub is_base64_encoded: bool, +} + +/// `ApiGatewayWebsocketProxyRequestContext` contains the information to identify +/// the AWS account and resources invoking the 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 ApiGatewayWebsocketProxyRequestContext +where + T1: DeserializeOwned, + T1: Serialize, + T2: DeserializeOwned, + T2: Serialize, +{ + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub identity: ApiGatewayRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(bound = "")] + pub authorizer: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + /// The API Gateway rest API Id + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + pub connected_at: i64, + #[serde(default)] + pub connection_id: Option, + #[serde(default)] + pub domain_name: Option, + #[serde(default)] + pub error: Option, + #[serde(default)] + pub event_type: Option, + #[serde(default)] + pub extended_request_id: Option, + #[serde(default)] + pub integration_latency: Option, + #[serde(default)] + pub message_direction: Option, + #[serde(bound = "")] + pub message_id: Option, + #[serde(default)] + pub request_time: Option, + pub request_time_epoch: i64, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub status: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentity` contains identity information for the request caller including certificate information if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentity { + #[serde(default)] + pub api_key_id: Option, + #[serde(default)] + pub api_key: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub client_cert: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert` contains certificate information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCert { + #[serde(default)] + pub client_cert_pem: Option, + #[serde(default)] + #[serde(rename = "issuerDN")] + pub issuer_dn: Option, + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + #[serde(rename = "subjectDN")] + pub subject_dn: Option, + pub validity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity` contains certificate validity information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestIdentityClientCertValidity { + #[serde(default)] + pub not_after: Option, + #[serde(default)] + pub not_before: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthentication` contains authentication context information for the request caller including client certificate information if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthentication { + #[serde(default)] + pub client_cert: Option, +} + +/// `ApiGatewayV2httpRequestContextAuthenticationClientCert` contains client certificate information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthenticationClientCert { + #[serde(default)] + pub client_cert_pem: Option, + #[serde(default)] + #[serde(rename = "issuerDN")] + pub issuer_dn: Option, + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + #[serde(rename = "subjectDN")] + pub subject_dn: Option, + pub validity: ApiGatewayV2httpRequestContextAuthenticationClientCertValidity, +} + +/// `ApiGatewayV2httpRequestContextAuthenticationClientCertValidity` contains client certificate validity information for the request caller if using mTLS. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2httpRequestContextAuthenticationClientCertValidity { + #[serde(default)] + pub not_after: Option, + #[serde(default)] + pub not_before: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext { + #[serde(default)] + pub path: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + pub identity: ApiGatewayCustomAuthorizerRequestTypeRequestIdentity, + #[serde(default)] + pub resource_path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV1Request { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, + #[serde(default)] + pub identity_source: Option, + #[serde(default)] + pub authorization_token: Option, + #[serde(default)] + pub resource: Option, + #[serde(default)] + pub path: Option, + #[serde(with = "http_method")] + pub http_method: Method, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub request_context: ApiGatewayV2CustomAuthorizerV1RequestTypeRequestContext, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerV2Request { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub route_arn: Option, + pub identity_source: Vec, + #[serde(default)] + pub route_key: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + #[serde(default)] + pub cookies: Vec, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + pub request_context: ApiGatewayV2httpRequestContext, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, +} + +/// `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"] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerContext { + pub principal_id: Option, + pub string_key: Option, + pub num_key: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub bool_key: bool, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequestContext` represents the expected format of an API Gateway custom authorizer response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequestContext { + #[serde(default)] + pub path: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub resource_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub identity: Option, + #[serde(default)] + pub resource_path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, +} + +/// `ApiGatewayCustomAuthorizerRequest` contains data coming in to a custom API Gateway authorizer function. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequest { + #[serde(default)] + pub type_: Option, + #[serde(default)] + pub authorization_token: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, +} + +/// `ApiGatewayCustomAuthorizerRequestTypeRequest` contains data coming in to a custom API Gateway authorizer function. +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerRequestTypeRequest { + #[serde(default)] + pub type_: Option, + /// nolint: stylecheck + #[serde(default)] + pub method_arn: Option, + #[serde(default)] + pub resource: Option, + #[serde(default)] + pub path: Option, + #[serde(deserialize_with = "http_method::deserialize_optional")] + #[serde(serialize_with = "http_method::serialize_optional")] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default)] + pub http_method: Option, + #[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, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub query_string_parameters: QueryMap, + #[serde(default, deserialize_with = "query_map::serde::standard::deserialize_empty")] + pub multi_value_query_string_parameters: QueryMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub path_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub stage_variables: HashMap, + pub request_context: ApiGatewayCustomAuthorizerRequestTypeRequestContext, +} + +/// `ApiGatewayCustomAuthorizerResponse` represents the expected format of an API Gateway authorization response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayCustomAuthorizerResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub principal_id: Option, + pub policy_document: ApiGatewayCustomAuthorizerPolicy, + #[serde(bound = "", default)] + pub context: T1, + pub usage_identifier_key: Option, +} + +/// `ApiGatewayV2CustomAuthorizerSimpleResponse` represents the simple format of an API Gateway V2 authorization response. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerSimpleResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + pub is_authorized: bool, + #[serde(bound = "", default)] + pub context: T1, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiGatewayV2CustomAuthorizerIamPolicyResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub principal_id: Option, + pub policy_document: ApiGatewayCustomAuthorizerPolicy, + #[serde(bound = "", default)] + pub context: T1, +} + +/// `ApiGatewayCustomAuthorizerPolicy` represents an IAM policy +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +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")] + pub action: Vec, + #[serde(default)] + #[serde(rename = "Effect")] + pub effect: Option, + #[serde(rename = "Resource")] + pub resource: Vec, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request_type_request() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-request-type-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request_type_request_websocket() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-websocket-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequestTypeRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_request() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-request.json"); + let parsed: ApiGatewayCustomAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayCustomAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_custom_auth_response() { + let data = include_bytes!("../../fixtures/example-apigw-custom-auth-response.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() { + let data = include_bytes!("../../fixtures/example-apigw-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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_response() { + let data = include_bytes!("../../fixtures/example-apigw-response.json"); + let parsed: ApiGatewayProxyResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ApiGatewayProxyResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_restapi_openapi_request() { + let data = include_bytes!("../../fixtures/example-apigw-restapi-openapi-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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_iam() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-iam.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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_jwt_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-jwt-authorizer.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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_lambda_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-lambda-authorizer.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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_request_no_authorizer() { + let data = include_bytes!("../../fixtures/example-apigw-v2-request-no-authorizer.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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request.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_console_test_request() { + let data = include_bytes!("../../fixtures/example-apigw-console-test-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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_websocket_request_without_method() { + let data = include_bytes!("../../fixtures/example-apigw-websocket-request-without-method.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() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v1-request.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); + } + + #[test] + #[cfg(feature = "apigw")] + fn example_apigw_v2_custom_authorizer_v2_request() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request.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_v2_custom_authorizer_v2_request_without_cookies() { + let data = include_bytes!("../../fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.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); + } +} diff --git a/lambda-events/src/event/appsync/mod.rs b/lambda-events/src/event/appsync/mod.rs new file mode 100644 index 00000000..0ef67b7b --- /dev/null +++ b/lambda-events/src/event/appsync/mod.rs @@ -0,0 +1,165 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// 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 +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub version: Option, + pub operation: AppSyncOperation, + #[serde(bound = "")] + pub payload: Option, +} + +/// `AppSyncIamIdentity` contains information about the caller authed via IAM. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncIamIdentity { + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub cognito_identity_auth_provider: Option, + #[serde(default)] + pub cognito_identity_auth_type: Option, + #[serde(default)] + pub cognito_identity_pool_id: Option, + #[serde(default)] + pub cognito_identity_id: Option, + pub source_ip: Vec, + #[serde(default)] + pub username: Option, + #[serde(default)] + pub user_arn: Option, +} + +/// `AppSyncCognitoIdentity` contains information about the caller authed via Cognito. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncCognitoIdentity +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub sub: Option, + #[serde(default)] + pub issuer: Option, + #[serde(default)] + pub username: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub claims: HashMap, + pub source_ip: Vec, + #[serde(default)] + pub default_auth_strategy: Option, +} + +pub type AppSyncOperation = String; + +/// `AppSyncLambdaAuthorizerRequest` contains an authorization request from AppSync. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerRequest { + #[serde(default)] + pub authorization_token: Option, + pub request_context: AppSyncLambdaAuthorizerRequestContext, +} + +/// `AppSyncLambdaAuthorizerRequestContext` contains the parameters of the AppSync invocation which triggered +/// this authorization request. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerRequestContext +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub request_id: Option, + #[serde(default)] + pub query_string: Option, + #[serde(default)] + pub operation_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub variables: HashMap, +} + +/// `AppSyncLambdaAuthorizerResponse` represents the expected format of an authorization response to AppSync. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AppSyncLambdaAuthorizerResponse +where + T1: DeserializeOwned, + T1: Serialize, +{ + pub is_authorized: bool, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub resolver_context: HashMap, + pub denied_fields: Option>, + pub ttl_override: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_identity_cognito() { + let data = include_bytes!("../../fixtures/example-appsync-identity-cognito.json"); + let parsed: AppSyncCognitoIdentity = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncCognitoIdentity = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_identity_iam() { + let data = include_bytes!("../../fixtures/example-appsync-identity-iam.json"); + let parsed: AppSyncIamIdentity = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncIamIdentity = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_lambda_auth_request() { + let data = include_bytes!("../../fixtures/example-appsync-lambda-auth-request.json"); + let parsed: AppSyncLambdaAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncLambdaAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "appsync")] + fn example_appsync_lambda_auth_response() { + let data = include_bytes!("../../fixtures/example-appsync-lambda-auth-response.json"); + let parsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AppSyncLambdaAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/autoscaling/mod.rs b/lambda-events/src/event/autoscaling/mod.rs new file mode 100644 index 00000000..ce0128c2 --- /dev/null +++ b/lambda-events/src/event/autoscaling/mod.rs @@ -0,0 +1,111 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `AutoScalingEvent` struct is used to parse the json for auto scaling event types // +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoScalingEvent +where + T1: DeserializeOwned, + T1: Serialize, +{ + /// The version of event data + #[serde(default)] + pub version: Option, + /// The unique ID of the event + #[serde(default)] + pub id: Option, + /// Details about event type + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source of the event + #[serde(default)] + pub source: Option, + /// AccountId + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Event timestamp + pub time: DateTime, + /// Region of event + #[serde(default)] + pub region: Option, + /// Information about resources impacted by event + pub resources: Vec, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub detail: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_launch_successful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-launch-successful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_launch_unsuccessful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-launch-unsuccessful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_lifecycle_action() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-lifecycle-action.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_action() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-action.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_successful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-successful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "autoscaling")] + fn example_autoscaling_event_terminate_unsuccessful() { + let data = include_bytes!("../../fixtures/example-autoscaling-event-terminate-unsuccessful.json"); + let parsed: AutoScalingEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: AutoScalingEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/chime_bot/mod.rs b/lambda-events/src/event/chime_bot/mod.rs new file mode 100644 index 00000000..ef57c6f9 --- /dev/null +++ b/lambda-events/src/event/chime_bot/mod.rs @@ -0,0 +1,52 @@ +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEvent { + #[serde(rename = "Sender")] + pub sender: ChimeBotEventSender, + #[serde(rename = "Discussion")] + pub discussion: ChimeBotEventDiscussion, + #[serde(default)] + #[serde(rename = "EventType")] + pub event_type: Option, + #[serde(rename = "InboundHttpsEndpoint")] + pub inbound_https_endpoint: Option, + #[serde(rename = "EventTimestamp")] + pub event_timestamp: DateTime, + #[serde(rename = "Message")] + pub message: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventSender { + #[serde(default)] + #[serde(rename = "SenderId")] + pub sender_id: Option, + #[serde(default)] + #[serde(rename = "SenderIdType")] + pub sender_id_type: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventDiscussion { + #[serde(default)] + #[serde(rename = "DiscussionId")] + pub discussion_id: Option, + #[serde(default)] + #[serde(rename = "DiscussionType")] + pub discussion_type: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ChimeBotEventInboundHttpsEndpoint { + #[serde(default)] + #[serde(rename = "EndpointType")] + pub endpoint_type: Option, + #[serde(default)] + #[serde(rename = "Url")] + pub url: Option, +} diff --git a/lambda-events/src/event/clientvpn/mod.rs b/lambda-events/src/event/clientvpn/mod.rs new file mode 100644 index 00000000..f0e61dda --- /dev/null +++ b/lambda-events/src/event/clientvpn/mod.rs @@ -0,0 +1,61 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientVpnConnectionHandlerRequest { + #[serde(default)] + #[serde(rename = "connection-id")] + pub connection_id: Option, + #[serde(default)] + #[serde(rename = "endpoint-id")] + pub endpoint_id: Option, + #[serde(default)] + #[serde(rename = "common-name")] + pub common_name: Option, + #[serde(default)] + pub username: Option, + #[serde(default)] + #[serde(rename = "platform")] + pub os_platform: Option, + #[serde(default)] + #[serde(rename = "platform-version")] + pub os_platform_version: Option, + #[serde(default)] + #[serde(rename = "public-ip")] + pub public_ip: Option, + #[serde(default)] + #[serde(rename = "client-openvpn-version")] + pub client_open_vpn_version: Option, + #[serde(default)] + #[serde(rename = "schema-version")] + pub schema_version: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClientVpnConnectionHandlerResponse { + pub allow: bool, + #[serde(default)] + #[serde(rename = "error-msg-on-failed-posture-compliance")] + pub error_msg_on_failed_posture_compliance: Option, + #[serde(rename = "posture-compliance-statuses")] + pub posture_compliance_statuses: Vec, + #[serde(default)] + #[serde(rename = "schema-version")] + pub schema_version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "clientvpn")] + fn example_clientvpn_connectionhandler_request() { + let data = include_bytes!("../../fixtures/example-clientvpn-connectionhandler-request.json"); + let parsed: ClientVpnConnectionHandlerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ClientVpnConnectionHandlerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/cloudwatch_events/cloudtrail.rs b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs new file mode 100644 index 00000000..aefa7f4a --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/cloudtrail.rs @@ -0,0 +1,49 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; +use serde_json::Value; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AWSAPICall { + pub event_version: String, + pub user_identity: UserIdentity, + pub event_time: String, + pub event_source: String, + pub event_name: String, + pub aws_region: String, + #[serde(rename = "sourceIPAddress")] + pub source_ipaddress: String, + pub user_agent: String, + pub request_parameters: I, + pub response_elements: Option, + #[serde(default)] + pub additional_event_data: Option, + #[serde(rename = "requestID")] + pub request_id: String, + #[serde(rename = "eventID")] + pub event_id: String, + pub event_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + 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, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Attributes { + pub mfa_authenticated: String, + pub creation_date: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/codedeploy.rs b/lambda-events/src/event/cloudwatch_events/codedeploy.rs new file mode 100644 index 00000000..0dd2b540 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/codedeploy.rs @@ -0,0 +1,35 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StateChangeNotification { + pub instance_group_id: String, + pub region: String, + pub application: String, + pub deployment_id: String, + pub state: String, + pub deployment_group: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentStateChangeNotification { + pub instance_id: String, + pub region: String, + pub state: String, + pub application: String, + pub deployment_id: String, + pub instance_group_id: String, + pub deployment_group: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChangeNotification { + pub pipeline: String, + pub version: String, + pub state: String, + #[serde(rename = "execution-id")] + pub execution_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/codepipeline.rs b/lambda-events/src/event/cloudwatch_events/codepipeline.rs new file mode 100644 index 00000000..86a1de15 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/codepipeline.rs @@ -0,0 +1,47 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PipelineExecutionStateChange { + pub pipeline: String, + pub version: String, + pub state: String, + #[serde(rename = "execution-id")] + pub execution_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StageExecutionStateChange { + pub pipeline: String, + pub version: String, + #[serde(rename = "execution-id")] + pub execution_id: String, + pub stage: String, + pub state: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionExecutionStateChange { + pub pipeline: String, + pub version: i64, + #[serde(rename = "execution-id")] + pub execution_id: String, + pub stage: String, + pub action: String, + pub state: String, + pub region: String, + #[serde(rename = "type")] + pub type_field: ActionExecutionStateChangeType, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionExecutionStateChangeType { + pub owner: String, + pub category: String, + pub provider: String, + pub version: i64, +} diff --git a/lambda-events/src/event/cloudwatch_events/ec2.rs b/lambda-events/src/event/cloudwatch_events/ec2.rs new file mode 100644 index 00000000..c4e26b4e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/ec2.rs @@ -0,0 +1,10 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChange { + #[serde(rename = "instance-id")] + pub instance_id: String, + pub state: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/emr.rs b/lambda-events/src/event/cloudwatch_events/emr.rs new file mode 100644 index 00000000..942e5984 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/emr.rs @@ -0,0 +1,50 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AutoScalingPolicyStateChange { + pub resource_id: String, + pub cluster_id: String, + pub state: String, + pub message: String, + pub scaling_resource_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ClusterStateChange { + pub severity: String, + pub state_change_reason: String, + pub name: String, + pub cluster_id: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceGroupStateChange { + pub market: String, + pub severity: String, + pub requested_instance_count: String, + pub instance_type: String, + pub instance_group_type: String, + pub instance_group_id: String, + pub cluster_id: String, + pub running_instance_count: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StepStatusChange { + pub severity: String, + pub action_on_failure: String, + pub step_id: String, + pub name: String, + pub cluster_id: String, + pub state: String, + pub message: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/gamelift.rs b/lambda-events/src/event/cloudwatch_events/gamelift.rs new file mode 100644 index 00000000..1369a793 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/gamelift.rs @@ -0,0 +1,119 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +use crate::custom_serde::deserialize_nullish_boolean; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingSearching { + pub tickets: Vec, + pub estimated_wait_millis: String, + pub r#type: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Ticket { + pub ticket_id: String, + pub start_time: String, + pub players: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Player { + pub player_id: String, + pub team: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub accepted: bool, + pub player_session_id: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GameSessionInfo { + pub players: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PotentialMatchCreated { + pub tickets: Vec, + pub acceptance_timeout: i64, + pub rule_evaluation_metrics: Vec, + pub acceptance_required: bool, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RuleEvaluationMetric { + pub rule_name: String, + pub passed_count: i64, + pub failed_count: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptMatch { + pub tickets: Vec, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AcceptMatchCompleted { + pub tickets: Vec, + pub acceptance: String, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingSucceeded { + pub tickets: Vec, + pub r#type: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingTimedOut { + pub reason: String, + pub tickets: Vec, + pub rule_evaluation_metrics: Vec, + pub r#type: String, + pub message: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingCancelled { + pub reason: String, + pub tickets: Vec, + pub rule_evaluation_metrics: Vec, + pub r#type: String, + pub message: String, + pub game_session_info: GameSessionInfo, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MatchmakingFailed { + pub tickets: Vec, + pub custom_event_data: String, + pub r#type: String, + pub reason: String, + pub message: String, + pub game_session_info: GameSessionInfo, + pub match_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/glue.rs b/lambda-events/src/event/cloudwatch_events/glue.rs new file mode 100644 index 00000000..f752f53e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/glue.rs @@ -0,0 +1,89 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobRunStateChange { + pub job_name: String, + pub severity: String, + pub state: String, + pub job_run_id: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerStarted { + pub account_id: String, + pub crawler_name: String, + pub start_time: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerSucceeded { + pub tables_created: String, + pub warning_message: String, + pub partitions_updated: String, + pub tables_updated: String, + pub message: String, + pub partitions_deleted: String, + pub account_id: String, + #[serde(rename = "runningTime (sec)")] + pub running_time_sec: String, + pub tables_deleted: String, + pub crawler_name: String, + pub completion_date: String, + pub state: String, + pub partitions_created: String, + pub cloud_watch_log_link: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CrawlerFailed { + pub crawler_name: String, + pub error_message: String, + pub account_id: String, + pub cloud_watch_log_link: String, + pub state: String, + pub message: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobRunStatus { + pub job_name: String, + pub severity: String, + pub notification_condition: NotificationCondition, + pub state: String, + pub job_run_id: String, + pub message: String, + pub started_on: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct NotificationCondition { + #[serde(rename = "NotifyDelayAfter")] + pub notify_delay_after: f64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataCatalogTableStateChange { + pub database_name: String, + pub changed_partitions: Vec, + pub type_of_change: String, + pub table_name: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DataCatalogDatabaseStateChange { + pub database_name: String, + pub type_of_change: String, + pub changed_tables: Vec, +} diff --git a/lambda-events/src/event/cloudwatch_events/health.rs b/lambda-events/src/event/cloudwatch_events/health.rs new file mode 100644 index 00000000..3c8acbf9 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/health.rs @@ -0,0 +1,31 @@ +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 { + pub event_arn: String, + pub service: String, + pub event_type_code: String, + pub event_type_category: String, + pub start_time: String, + pub end_time: String, + pub event_description: Vec, + pub affected_entities: Option>, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EventDescription { + pub language: String, + pub latest_description: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Entity { + pub entity_value: String, + pub tags: HashMap, +} diff --git a/lambda-events/src/event/cloudwatch_events/kms.rs b/lambda-events/src/event/cloudwatch_events/kms.rs new file mode 100644 index 00000000..ac6f8926 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/kms.rs @@ -0,0 +1,9 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CMKEvent { + #[serde(rename = "key-id")] + pub key_id: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/macie.rs b/lambda-events/src/event/cloudwatch_events/macie.rs new file mode 100644 index 00000000..4ce78b71 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/macie.rs @@ -0,0 +1,223 @@ +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 { + #[serde(rename = "notification-type")] + pub notification_type: String, + pub name: String, + pub tags: Vec, + pub url: String, + #[serde(rename = "alert-arn")] + pub alert_arn: String, + #[serde(rename = "risk-score")] + pub risk_score: i64, + pub trigger: Trigger, + #[serde(rename = "created-at")] + pub created_at: String, + pub actor: String, + pub summary: T, +} + +pub type BucketScanAlert = Alert; +pub type BucketWritableAlert = Alert; +pub type BucketContainsHighRiskObjectAlert = Alert; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Trigger { + #[serde(rename = "rule-arn")] + pub rule_arn: String, + #[serde(rename = "alert-type")] + pub alert_type: String, + #[serde(rename = "created-at")] + pub created_at: String, + pub description: String, + pub risk: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketScanSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "IP")] + pub ip: Ip, + #[serde(rename = "Time Range")] + pub time_range: Vec, + #[serde(rename = "Source ARN")] + pub source_arn: String, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "Location")] + pub location: Location, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "Events")] + pub events: HashMap, + pub recipient_account_id: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Ip { + #[serde(rename = "34.199.185.34")] + pub n34_199_185_34: i64, + #[serde(rename = "34.205.153.2")] + pub n34_205_153_2: i64, + #[serde(rename = "72.21.196.70")] + pub n72_21_196_70: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeRange { + pub count: i64, + pub start: String, + pub end: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Location { + #[serde(rename = "us-east-1")] + pub us_east_1: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ActionInfo { + pub count: i64, + #[serde(rename = "ISP")] + pub isp: HashMap, + pub error_code: Option>, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketWritableSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "Bucket")] + pub bucket: Bucket, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "ACL")] + pub acl: Acl, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "Timestamps")] + pub timestamps: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Bucket { + #[serde(rename = "secret-bucket-name")] + pub secret_bucket_name: i64, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Acl { + #[serde(rename = "secret-bucket-name")] + pub secret_bucket_name: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SecretBucketName { + #[serde(rename = "Owner")] + pub owner: Owner, + #[serde(rename = "Grants")] + pub grants: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Owner { + #[serde(rename = "DisplayName")] + pub display_name: String, + #[serde(rename = "ID")] + pub id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Grant { + #[serde(rename = "Grantee")] + pub grantee: Grantee, + #[serde(rename = "Permission")] + pub permission: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Grantee { + pub r#type: String, + #[serde(rename = "URI")] + pub uri: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BucketContainsHighRiskObjectSummary { + #[serde(rename = "Description")] + pub description: String, + #[serde(rename = "Object")] + pub object: HashMap, + #[serde(rename = "Record Count")] + pub record_count: i64, + #[serde(rename = "Themes")] + pub themes: HashMap, + #[serde(rename = "Event Count")] + pub event_count: i64, + #[serde(rename = "DLP risk")] + pub dlp_risk: HashMap, + #[serde(rename = "Owner")] + pub owner: HashMap, + #[serde(rename = "Timestamps")] + pub timestamps: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AlertUpdated { + #[serde(rename = "notification-type")] + pub notification_type: String, + pub name: String, + pub tags: Vec, + pub url: String, + #[serde(rename = "alert-arn")] + pub alert_arn: String, + #[serde(rename = "risk-score")] + pub risk_score: i64, + #[serde(rename = "created-at")] + pub created_at: String, + pub actor: String, + pub trigger: UpdatedTrigger, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UpdatedTrigger { + #[serde(rename = "alert-type")] + pub alert_type: String, + pub features: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FeatureInfo { + pub name: String, + pub description: String, + pub narrative: String, + pub anomalous: bool, + pub multiplier: f64, + #[serde(rename = "excession_times")] + pub excession_times: Vec, + pub risk: i64, +} diff --git a/lambda-events/src/event/cloudwatch_events/mod.rs b/lambda-events/src/event/cloudwatch_events/mod.rs new file mode 100644 index 00000000..3c39e3d3 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/mod.rs @@ -0,0 +1,50 @@ +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; + +pub mod cloudtrail; +pub mod codedeploy; +pub mod codepipeline; +pub mod ec2; +pub mod emr; +pub mod gamelift; +pub mod glue; +pub mod health; +pub mod kms; +pub mod macie; +pub mod opsworks; +pub mod signin; +pub mod sms; +pub mod ssm; +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 +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CloudWatchEvent +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + pub time: DateTime, + #[serde(default)] + pub region: Option, + pub resources: Vec, + #[serde(bound = "")] + pub detail: Option, +} diff --git a/lambda-events/src/event/cloudwatch_events/opsworks.rs b/lambda-events/src/event/cloudwatch_events/opsworks.rs new file mode 100644 index 00000000..c75f1b5e --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/opsworks.rs @@ -0,0 +1,55 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InstanceStateChange { + #[serde(rename = "initiated_by")] + pub initiated_by: String, + pub hostname: String, + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "layer-ids")] + pub layer_ids: Vec, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "ec2-instance-id")] + pub ec2_instance_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CommandStateChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + pub r#type: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DeploymentStateChange { + pub duration: i64, + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "instance-ids")] + pub instance_ids: Vec, + #[serde(rename = "deployment-id")] + pub deployment_id: String, + pub command: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Alert { + #[serde(rename = "stack-id")] + pub stack_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + pub r#type: String, + pub message: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/signin.rs b/lambda-events/src/event/cloudwatch_events/signin.rs new file mode 100644 index 00000000..4d256e3b --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/signin.rs @@ -0,0 +1,50 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; +use serde_json::Value; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SignIn { + pub event_version: String, + pub user_identity: UserIdentity, + pub event_time: String, + pub event_source: String, + pub event_name: String, + pub aws_region: String, + #[serde(rename = "sourceIPAddress")] + pub source_ipaddress: String, + pub user_agent: String, + pub request_parameters: Value, + pub response_elements: ResponseElements, + pub additional_event_data: AdditionalEventData, + #[serde(rename = "eventID")] + pub event_id: String, + pub event_type: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResponseElements { + #[serde(rename = "ConsoleLogin")] + pub console_login: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AdditionalEventData { + #[serde(rename = "LoginTo")] + pub login_to: String, + #[serde(rename = "MobileVersion")] + pub mobile_version: String, + #[serde(rename = "MFAUsed")] + pub mfaused: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/sms.rs b/lambda-events/src/event/cloudwatch_events/sms.rs new file mode 100644 index 00000000..33092b76 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/sms.rs @@ -0,0 +1,15 @@ +use serde_derive::Deserialize; +use serde_derive::Serialize; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JobStateChange { + pub state: String, + #[serde(rename = "replication-run-id")] + pub replication_run_id: String, + #[serde(rename = "replication-job-id")] + pub replication_job_id: String, + #[serde(rename = "ami-id")] + pub ami_id: Option, + pub version: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/ssm.rs b/lambda-events/src/event/cloudwatch_events/ssm.rs new file mode 100644 index 00000000..a826ed07 --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/ssm.rs @@ -0,0 +1,240 @@ +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 { + #[serde(rename = "ExecutionId")] + pub execution_id: String, + #[serde(rename = "Definition")] + pub definition: String, + #[serde(rename = "DefinitionVersion")] + pub definition_version: f64, + #[serde(rename = "Status")] + pub status: String, + #[serde(rename = "EndTime")] + pub end_time: String, + #[serde(rename = "StartTime")] + pub start_time: String, + #[serde(rename = "Time")] + pub time: f64, + #[serde(rename = "StepName")] + pub step_name: String, + #[serde(rename = "Action")] + pub action: String, +} + +#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2AutomationExecutionStatusChange { + #[serde(rename = "ExecutionId")] + pub execution_id: String, + #[serde(rename = "Definition")] + pub definition: String, + #[serde(rename = "DefinitionVersion")] + pub definition_version: f64, + #[serde(rename = "Status")] + pub status: String, + #[serde(rename = "StartTime")] + pub start_time: String, + #[serde(rename = "EndTime")] + pub end_time: String, + #[serde(rename = "Time")] + pub time: f64, + #[serde(rename = "ExecutedBy")] + pub executed_by: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct StateChange { + pub state: String, + pub at_time: String, + pub next_transition_time: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigurationComplianceStateChange { + #[serde(rename = "last-runtime")] + pub last_runtime: Option, + #[serde(rename = "compliance-status")] + pub compliance_status: String, + #[serde(rename = "resource-type")] + pub resource_type: String, + #[serde(rename = "resource-id")] + pub resource_id: String, + #[serde(rename = "compliance-type")] + pub compliance_type: String, + #[serde(rename = "patch-baseline-id")] + pub patch_baseline_id: Option, + pub serverity: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTargetRegistration { + #[serde(rename = "window-target-id")] + pub window_target_id: String, + #[serde(rename = "window-id")] + pub window_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowExecutionStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTaskExecutionStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "task-execution-id")] + pub task_execution_id: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowTaskTargetInvocationStateChange { + #[serde(rename = "start-time")] + pub start_time: String, + #[serde(rename = "end-time")] + pub end_time: String, + #[serde(rename = "window-id")] + pub window_id: String, + #[serde(rename = "window-execution-id")] + pub window_execution_id: String, + #[serde(rename = "task-execution-id")] + pub task_execution_id: String, + #[serde(rename = "window-target-id")] + pub window_target_id: String, + pub status: String, + #[serde(rename = "owner-information")] + pub owner_information: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MaintenanceWindowStateChange { + #[serde(rename = "window-id")] + pub window_id: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ParameterStoreStateChange { + pub operation: String, + pub name: String, + pub r#type: String, + pub description: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2CommandStatusChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "expire-after")] + pub expire_after: String, + pub parameters: HashMap, + #[serde(rename = "requested-date-time")] + pub requested_date_time: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2CommandInvocationStatusChange { + #[serde(rename = "command-id")] + pub command_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "requested-date-time")] + pub requested_date_time: String, + pub status: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2StateManagerAssociationStateChange { + #[serde(rename = "association-id")] + pub association_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "association-version")] + pub association_version: String, + #[serde(rename = "document-version")] + pub document_version: String, + pub targets: String, + #[serde(rename = "creation-date")] + pub creation_date: String, + #[serde(rename = "last-successful-execution-date")] + pub last_successful_execution_date: String, + #[serde(rename = "last-execution-date")] + pub last_execution_date: String, + #[serde(rename = "last-updated-date")] + pub last_updated_date: String, + pub status: String, + #[serde(rename = "association-status-aggregated-count")] + pub association_status_aggregated_count: String, + #[serde(rename = "schedule-expression")] + pub schedule_expression: String, + #[serde(rename = "association-cwe-version")] + pub association_cwe_version: String, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EC2StateManagerInstanceAssociationStateChange { + #[serde(rename = "association-id")] + pub association_id: String, + #[serde(rename = "instance-id")] + pub instance_id: String, + #[serde(rename = "document-name")] + pub document_name: String, + #[serde(rename = "document-version")] + pub document_version: String, + pub targets: String, + #[serde(rename = "creation-date")] + pub creation_date: String, + #[serde(rename = "last-successful-execution-date")] + pub last_successful_execution_date: String, + #[serde(rename = "last-execution-date")] + pub last_execution_date: String, + pub status: String, + #[serde(rename = "detailed-status")] + pub detailed_status: String, + #[serde(rename = "error-code")] + pub error_code: String, + #[serde(rename = "execution-summary")] + pub execution_summary: String, + #[serde(rename = "output-url")] + pub output_url: String, + #[serde(rename = "instance-association-cwe-version")] + pub instance_association_cwe_version: String, +} diff --git a/lambda-events/src/event/cloudwatch_events/tag.rs b/lambda-events/src/event/cloudwatch_events/tag.rs new file mode 100644 index 00000000..573a99ea --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/tag.rs @@ -0,0 +1,16 @@ +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 TagChangeOnResource { + #[serde(rename = "changed-tag-keys")] + pub changed_tag_keys: Vec, + pub service: String, + #[serde(rename = "resource-type")] + pub resource_type: String, + pub version: i64, + pub tags: HashMap, +} diff --git a/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs new file mode 100644 index 00000000..ce6cf79f --- /dev/null +++ b/lambda-events/src/event/cloudwatch_events/trustedadvisor.rs @@ -0,0 +1,17 @@ +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 { + #[serde(rename = "check-name")] + pub check_name: String, + #[serde(rename = "check-item-detail")] + pub check_item_detail: HashMap, + pub status: String, + #[serde(rename = "resource_id")] + pub resource_id: String, + pub uuid: String, +} diff --git a/lambda-events/src/event/cloudwatch_logs/mod.rs b/lambda-events/src/event/cloudwatch_logs/mod.rs new file mode 100644 index 00000000..e8371c8f --- /dev/null +++ b/lambda-events/src/event/cloudwatch_logs/mod.rs @@ -0,0 +1,157 @@ +use serde::{ + de::{Error, MapAccess, Visitor}, + ser::{Error as SeError, SerializeStruct}, + Deserialize, Deserializer, Serialize, Serializer, +}; +use std::{fmt, io::BufReader}; + +/// `LogsEvent` represents the raw event sent by CloudWatch +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct LogsEvent { + /// `aws_logs` is gzipped and base64 encoded, it needs a custom deserializer + #[serde(rename = "awslogs")] + pub aws_logs: AwsLogs, +} + +/// `AwsLogs` is an unmarshaled, ungzipped, CloudWatch logs event +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct AwsLogs { + /// `data` is the log data after is decompressed + pub data: LogData, +} + +/// `LogData` represents the logs group event information +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LogData { + /// Owner of the log event + pub owner: String, + /// Log group where the event was published + pub log_group: String, + /// Log stream where the event was published + pub log_stream: String, + /// Filters applied to the event + pub subscription_filters: Vec, + /// Type of event + pub message_type: String, + /// Entries in the log batch + pub log_events: Vec, +} + +/// `LogEntry` represents a log entry from cloudwatch logs +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct LogEntry { + /// Unique id for the entry + pub id: String, + /// Time when the event was published + pub timestamp: i64, + /// Message published in the application log + pub message: String, +} + +impl<'de> Deserialize<'de> for AwsLogs { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct AwsLogsVisitor; + + impl<'de> Visitor<'de> for AwsLogsVisitor { + type Value = AwsLogs; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a base64 gzipped string") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut data = None; + while let Some(key) = map.next_key()? { + match key { + "data" => { + let bytes = map + .next_value::() + .and_then(|string| base64::decode(string).map_err(Error::custom))?; + + let bytes = flate2::read::GzDecoder::new(&bytes[..]); + let mut de = serde_json::Deserializer::from_reader(BufReader::new(bytes)); + data = Some(LogData::deserialize(&mut de).map_err(Error::custom)?); + } + _ => return Err(Error::unknown_field(key, FIELDS)), + } + } + + let data = data.ok_or_else(|| Error::missing_field("data"))?; + Ok(AwsLogs { data }) + } + } + + const FIELDS: &[&str] = &["data"]; + deserializer.deserialize_struct("AwsLogs", FIELDS, AwsLogsVisitor) + } +} + +impl Serialize for AwsLogs { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let base = base64::write::EncoderWriter::new(Vec::new(), base64::STANDARD_NO_PAD); + let mut gzip = flate2::write::GzEncoder::new(base, flate2::Compression::default()); + + serde_json::to_writer(&mut gzip, &self.data).map_err(SeError::custom)?; + let mut base = gzip.finish().map_err(SeError::custom)?; + let data = base.finish().map_err(SeError::custom)?; + let string = std::str::from_utf8(data.as_slice()).map_err(SeError::custom)?; + + let mut state = serializer.serialize_struct("AwsLogs", 1)?; + state.serialize_field("data", &string)?; + state.end() + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[cfg(feature = "cloudwatch_logs")] + fn test_deserialize_example() { + let json = r#"{ + "awslogs": { + "data": "H4sIAFETomIAA12Ry27bMBBF9/4KQuiyqsQ36Z2DqEGBGC0sdRUHAS0NExV6uCJVNw3y76Fkx03CFTH3cubwztMChRO14Jy5h+JxD9ESRZerYnW3zvJ8dZVFn4+W/tDBMImYUMaFVDrF5FVs+vuroR/3k56Yg0sa0+4qk0D50MddX8Ev98aa+wFMO3lJinWS0gTT5ObT9arI8uJWM2uUkMCpZIxiorGRtsQMiOXCgHxt5MadK4d67+u++1o3HgYXWt7M4my4nhmOw+7Kph+rg/HlQwBwM1M0W2//c2V/oPPvmzydb7OpriZqygQhFItUa6GlUkymgrNUS5EKpQhRfMpGCEzC/xgWjCpNOBMn8nM3X4fcvWmn2DDnhGNFWXiffvCdtjON3mQ/vm8KtIHfY3j6rVoiEdaxsxZizLSJd4KRWGFrYwIKqBSVMtZu/eU4mCmoJWLii2KodVt/UTcNVOiNJEMdbf0a2n54RHn9DwKYJmh9EYrmLzoJPx2EwfJY33bRmfb5mOjiefECiB5LsVgCAAA=" + } +}"#; + let event: LogsEvent = serde_json::from_str(json).expect("failed to deserialize"); + let data = event.aws_logs.data.clone(); + assert_eq!("DATA_MESSAGE", data.message_type); + assert_eq!("123456789012", data.owner); + assert_eq!("/aws/lambda/echo-nodejs", data.log_group); + assert_eq!("2019/03/13/[$LATEST]94fa867e5374431291a7fc14e2f56ae7", data.log_stream); + assert_eq!(1, data.subscription_filters.len()); + assert_eq!("LambdaStream_cloudwatchlogs-node", data.subscription_filters[0]); + assert_eq!(1, data.log_events.len()); + assert_eq!( + "34622316099697884706540976068822859012661220141643892546", + data.log_events[0].id + ); + assert_eq!(1552518348220, data.log_events[0].timestamp); + assert_eq!("REPORT RequestId: 6234bffe-149a-b642-81ff-2e8e376d8aff\tDuration: 46.84 ms\tBilled Duration: 47 ms \tMemory Size: 192 MB\tMax Memory Used: 72 MB\t\n", data.log_events[0].message); + + let new_json = serde_json::to_string_pretty(&event).unwrap(); + let new_event: LogsEvent = serde_json::from_str(&new_json).expect("failed to deserialize"); + assert_eq!(new_event, event); + } + + #[test] + #[cfg(feature = "cloudwatch_logs")] + fn example_cloudwatch_logs_event() { + let data = include_bytes!("../../fixtures/example-cloudwatch_logs-event.json"); + let parsed: LogsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LogsEvent = 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 new file mode 100644 index 00000000..8b2e617f --- /dev/null +++ b/lambda-events/src/event/code_commit/mod.rs @@ -0,0 +1,82 @@ +use chrono::{DateTime, Utc}; + +use crate::custom_serde::deserialize_nullish_boolean; + +/// `CodeCommitEvent` represents a CodeCommit event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +pub type CodeCommitEventTime = DateTime; + +/// `CodeCommitRecord` represents a CodeCommit record +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitRecord { + #[serde(default)] + pub event_id: Option, + #[serde(default)] + pub event_version: Option, + pub event_time: CodeCommitEventTime, + #[serde(default)] + pub event_trigger_name: Option, + pub event_part_number: u64, + #[serde(rename = "codecommit")] + pub code_commit: CodeCommitCodeCommit, + #[serde(default)] + pub event_name: Option, + /// nolint: stylecheck + #[serde(default)] + pub event_trigger_config_id: Option, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + #[serde(rename = "userIdentityARN")] + pub user_identity_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, + pub event_total_parts: u64, + pub custom_data: Option, +} + +/// `CodeCommitCodeCommit` represents a CodeCommit object in a record +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitCodeCommit { + pub references: Vec, +} + +/// `CodeCommitReference` represents a Reference object in a CodeCommit object +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeCommitReference { + #[serde(default)] + pub commit: Option, + #[serde(default)] + pub ref_: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub created: bool, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "code_commit")] + fn example_code_commit_event() { + let data = include_bytes!("../../fixtures/example-code_commit-event.json"); + let parsed: CodeCommitEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeCommitEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codebuild/mod.rs b/lambda-events/src/event/codebuild/mod.rs new file mode 100644 index 00000000..2c6ddf39 --- /dev/null +++ b/lambda-events/src/event/codebuild/mod.rs @@ -0,0 +1,237 @@ +use crate::custom_serde::*; +use crate::encodings::{MinuteDuration, SecondDuration}; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; + +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 { + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// DetailType informs the schema of the Detail field. For build state-change + /// events, the value will be CodeBuildStateChangeDetailType. For phase-change + /// events, it will be CodeBuildPhaseChangeDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodeBuildEventSource. + #[serde(default)] + pub source: Option, + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// Resources is a list of ARNs of CodeBuild builds that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a build state-change or + /// build phase-change event. + pub detail: CodeBuildEventDetail, +} + +/// `CodeBuildEventDetail` represents the all details related to the code build event +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEventDetail { + #[serde(rename = "build-status")] + pub build_status: Option, + #[serde(default)] + #[serde(rename = "project-name")] + pub project_name: Option, + #[serde(default)] + #[serde(rename = "build-id")] + pub build_id: Option, + #[serde(rename = "additional-information")] + pub additional_information: CodeBuildEventAdditionalInformation, + #[serde(rename = "current-phase")] + pub current_phase: Option, + #[serde(default)] + #[serde(rename = "current-phase-context")] + pub current_phase_context: Option, + #[serde(default)] + pub version: Option, + #[serde(rename = "completed-phase-status")] + pub completed_phase_status: Option, + #[serde(rename = "completed-phase")] + pub completed_phase: Option, + #[serde(default)] + #[serde(rename = "completed-phase-context")] + pub completed_phase_context: Option, + #[serde(rename = "completed-phase-duration-seconds")] + pub completed_phase_duration: Option, + #[serde(rename = "completed-phase-start")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub completed_phase_start: Option, + #[serde(rename = "completed-phase-end")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub completed_phase_end: Option, +} + +/// `CodeBuildEventAdditionalInformation` represents additional information to the code build event +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEventAdditionalInformation { + pub artifact: CodeBuildArtifact, + pub environment: CodeBuildEnvironment, + #[serde(rename = "timeout-in-minutes")] + pub timeout: MinuteDuration, + #[serde(rename = "build-complete")] + pub build_complete: bool, + #[serde(rename = "build-number")] + pub build_number: Option, + #[serde(default)] + pub initiator: Option, + #[serde(rename = "build-start-time")] + #[serde(with = "codebuild_time::str_time")] + pub build_start_time: CodeBuildTime, + pub source: CodeBuildSource, + #[serde(default)] + #[serde(rename = "source-version")] + pub source_version: Option, + pub logs: CodeBuildLogs, + pub phases: Vec, +} + +/// `CodeBuildArtifact` represents the artifact provided to build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildArtifact { + #[serde(default)] + #[serde(rename = "md5sum")] + pub md5_sum: Option, + #[serde(default)] + #[serde(rename = "sha256sum")] + pub sha256_sum: Option, + #[serde(default)] + pub location: Option, +} + +/// `CodeBuildEnvironment` represents the environment for a build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEnvironment { + #[serde(default)] + pub image: Option, + #[serde(rename = "privileged-mode")] + pub privileged_mode: bool, + #[serde(default)] + #[serde(rename = "compute-type")] + pub compute_type: Option, + #[serde(default)] + pub type_: Option, + #[serde(rename = "environment-variables")] + pub environment_variables: Vec, +} + +/// `CodeBuildEnvironmentVariable` encapsulate environment variables for the code build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildEnvironmentVariable { + /// Name is the name of the environment variable. + #[serde(default)] + pub name: Option, + /// Type is PLAINTEXT or PARAMETER_STORE. + #[serde(default)] + pub type_: Option, + /// Value is the value of the environment variable. + #[serde(default)] + pub value: Option, +} + +/// `CodeBuildSource` represent the code source will be build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildSource { + #[serde(default)] + pub location: Option, + #[serde(default)] + pub type_: Option, +} + +/// `CodeBuildLogs` gives the log details of a code build +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildLogs { + #[serde(default)] + #[serde(rename = "group-name")] + pub group_name: Option, + #[serde(default)] + #[serde(rename = "stream-name")] + pub stream_name: Option, + #[serde(default)] + #[serde(rename = "deep-link")] + pub deep_link: Option, +} + +/// `CodeBuildPhase` represents the phase of a build and its details +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeBuildPhase +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(bound = "")] + #[serde(rename = "phase-context")] + pub phase_context: Option>, + #[serde(rename = "start-time")] + #[serde(with = "codebuild_time::str_time")] + pub start_time: CodeBuildTime, + #[serde(rename = "end-time")] + #[serde(default)] + #[serde(with = "codebuild_time::optional_time")] + pub end_time: Option, + #[serde(rename = "duration-in-seconds")] + pub duration: Option, + #[serde(rename = "phase-type")] + pub phase_type: CodeBuildPhaseType, + #[serde(rename = "phase-status")] + pub phase_status: Option, +} + +pub type CodeBuildTime = DateTime; + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codebuild")] + fn example_codebuild_phase_change() { + let data = include_bytes!("../../fixtures/example-codebuild-phase-change.json"); + let parsed: CodeBuildEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeBuildEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codebuild")] + fn example_codebuild_state_change() { + let data = include_bytes!("../../fixtures/example-codebuild-state-change.json"); + let parsed: CodeBuildEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeBuildEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codedeploy/mod.rs b/lambda-events/src/event/codedeploy/mod.rs new file mode 100644 index 00000000..61bfc665 --- /dev/null +++ b/lambda-events/src/event/codedeploy/mod.rs @@ -0,0 +1,92 @@ +use chrono::{DateTime, Utc}; + +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 { + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// DetailType informs the schema of the Detail field. For deployment state-change + /// events, the value should be equal to CodeDeployDeploymentEventDetailType. + /// For instance state-change events, the value should be equal to + /// CodeDeployInstanceEventDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodeDeployEventSource. + #[serde(default)] + pub source: Option, + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// Resources is a list of ARNs of CodeDeploy applications and deployment + /// groups that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a deployment event. + pub detail: CodeDeployEventDetail, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeDeployEventDetail { + /// InstanceGroupID is the ID of the instance group. + #[serde(default)] + pub instance_group_id: Option, + /// InstanceID is the id of the instance. This field is non-empty only if + /// the DetailType of the complete event is CodeDeployInstanceEventDetailType. + pub instance_id: Option, + /// Region is the AWS region that the event originated from. + #[serde(default)] + pub region: Option, + /// Application is the name of the CodeDeploy application. + #[serde(default)] + pub application: Option, + /// DeploymentID is the id of the deployment. + #[serde(default)] + pub deployment_id: Option, + /// State is the new state of the deployment. + pub state: CodeDeployDeploymentState, + /// DeploymentGroup is the name of the deployment group. + #[serde(default)] + pub deployment_group: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_deployment_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-deployment-event.json"); + let parsed: CodeDeployEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codedeploy")] + fn example_codedeploy_instance_event() { + let data = include_bytes!("../../fixtures/example-codedeploy-instance-event.json"); + let parsed: CodeDeployEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodeDeployEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codepipeline_cloudwatch/mod.rs b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs new file mode 100644 index 00000000..d4d3477b --- /dev/null +++ b/lambda-events/src/event/codepipeline_cloudwatch/mod.rs @@ -0,0 +1,114 @@ +use chrono::{DateTime, Utc}; + +pub type CodePipelineStageState = String; + +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 { + /// Version is the version of the event's schema. + #[serde(default)] + pub version: Option, + /// ID is the GUID of this event. + #[serde(default)] + pub id: Option, + /// DetailType informs the schema of the Detail field. For deployment state-change + /// events, the value should be equal to CodePipelineDeploymentEventDetailType. + /// For instance state-change events, the value should be equal to + /// CodePipelineInstanceEventDetailType. + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + /// Source should be equal to CodePipelineEventSource. + #[serde(default)] + pub source: Option, + /// AccountID is the id of the AWS account from which the event originated. + #[serde(default)] + #[serde(rename = "account")] + pub account_id: Option, + /// Time is the event's timestamp. + pub time: DateTime, + /// Region is the AWS region from which the event originated. + #[serde(default)] + pub region: Option, + /// Resources is a list of ARNs of CodePipeline applications and deployment + /// groups that this event pertains to. + pub resources: Vec, + /// Detail contains information specific to a deployment event. + pub detail: CodePipelineEventDetail, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineEventDetail { + #[serde(default)] + pub pipeline: Option, + /// From live testing this is always int64 not string as documented + pub version: i64, + #[serde(default)] + #[serde(rename = "execution-id")] + pub execution_id: Option, + #[serde(default)] + pub stage: Option, + #[serde(default)] + pub action: Option, + pub state: CodePipelineState, + #[serde(default)] + pub region: Option, + pub type_: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineEventDetailType { + #[serde(default)] + pub owner: Option, + #[serde(default)] + pub category: Option, + #[serde(default)] + pub provider: Option, + /// From published EventBridge schema registry this is always int64 not string as documented + pub version: i64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_action_execution_stage_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-action-execution-stage-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_execution_stage_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-execution-stage-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "codepipeline_cloudwatch")] + fn example_codepipeline_execution_state_change_event() { + let data = include_bytes!("../../fixtures/example-codepipeline-execution-state-change-event.json"); + let parsed: CodePipelineCloudWatchEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineCloudWatchEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/codepipeline_job/mod.rs b/lambda-events/src/event/codepipeline_job/mod.rs new file mode 100644 index 00000000..0767b272 --- /dev/null +++ b/lambda-events/src/event/codepipeline_job/mod.rs @@ -0,0 +1,129 @@ +/// `CodePipelineJobEvent` contains data from an event sent from AWS CodePipeline +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineJobEvent { + #[serde(rename = "CodePipeline.job")] + pub code_pipeline_job: CodePipelineJob, +} + +/// `CodePipelineJob` represents a job from an AWS CodePipeline event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineJob { + #[serde(default)] + pub id: Option, + #[serde(default)] + pub account_id: Option, + pub data: CodePipelineData, +} + +/// `CodePipelineData` represents a job from an AWS CodePipeline event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineData { + pub action_configuration: CodePipelineActionConfiguration, + pub input_artifacts: Vec, + #[serde(rename = "outputArtifacts")] + pub out_put_artifacts: Vec, + pub artifact_credentials: CodePipelineArtifactCredentials, + #[serde(default)] + pub continuation_token: Option, +} + +/// `CodePipelineActionConfiguration` represents an Action Configuration +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineActionConfiguration { + pub configuration: CodePipelineConfiguration, +} + +/// `CodePipelineConfiguration` represents a configuration for an Action Configuration +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineConfiguration { + #[serde(default)] + #[serde(rename = "FunctionName")] + pub function_name: Option, + #[serde(default)] + #[serde(rename = "UserParameters")] + pub user_parameters: Option, +} + +/// `CodePipelineInputArtifact` represents an input artifact +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineInputArtifact { + pub location: CodePipelineInputLocation, + pub revision: Option, + #[serde(default)] + pub name: Option, +} + +/// `CodePipelineInputLocation` represents a input location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineInputLocation { + pub s3_location: CodePipelineS3Location, + #[serde(default)] + #[serde(rename = "type")] + pub location_type: Option, +} + +/// `CodePipelineS3Location` represents an s3 input location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineS3Location { + #[serde(default)] + pub bucket_name: Option, + #[serde(default)] + pub object_key: Option, +} + +/// `CodePipelineOutputArtifact` represents an output artifact +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineOutputArtifact { + pub location: CodePipelineInputLocation, + pub revision: Option, + #[serde(default)] + pub name: Option, +} + +/// `CodePipelineOutputLocation` represents a output location +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineOutputLocation { + pub s3_location: CodePipelineS3Location, + #[serde(default)] + #[serde(rename = "type")] + pub location_type: Option, +} + +/// `CodePipelineArtifactCredentials` represents CodePipeline artifact credentials +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CodePipelineArtifactCredentials { + #[serde(default)] + pub secret_access_key: Option, + #[serde(default)] + pub session_token: Option, + #[serde(default)] + pub access_key_id: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "codepipeline_job")] + fn example_codepipeline_job_event() { + let data = include_bytes!("../../fixtures/example-codepipeline_job-event.json"); + let parsed: CodePipelineJobEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CodePipelineJobEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/cognito/mod.rs b/lambda-events/src/event/cognito/mod.rs new file mode 100644 index 00000000..99bc682b --- /dev/null +++ b/lambda-events/src/event/cognito/mod.rs @@ -0,0 +1,612 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `CognitoEvent` contains data from an event sent from AWS Cognito Sync +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEvent { + #[serde(default)] + pub dataset_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub dataset_records: HashMap, + #[serde(default)] + pub event_type: Option, + #[serde(default)] + pub identity_id: Option, + #[serde(default)] + pub identity_pool_id: Option, + #[serde(default)] + pub region: Option, + pub version: i64, +} + +/// `CognitoDatasetRecord` represents a record from an AWS Cognito Sync event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoDatasetRecord { + #[serde(default)] + pub new_value: Option, + #[serde(default)] + pub old_value: Option, + #[serde(default)] + pub op: Option, +} + +/// `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 +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignup { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreSignupRequest, + pub response: CognitoEventUserPoolsPreSignupResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreAuthentication { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreAuthenticationRequest, + pub response: CognitoEventUserPoolsPreAuthenticationResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostConfirmation { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPostConfirmationRequest, + pub response: CognitoEventUserPoolsPostConfirmationResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGen { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPreTokenGenRequest, + pub response: CognitoEventUserPoolsPreTokenGenResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostAuthentication { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsPostAuthenticationRequest, + pub response: CognitoEventUserPoolsPostAuthenticationResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUser { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + 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, +} + +/// `CognitoEventUserPoolsCallerContext` contains information about the caller +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCallerContext { + #[serde(default)] + #[serde(rename = "awsSdkVersion")] + pub awssdk_version: Option, + #[serde(default)] + pub client_id: Option, +} + +/// `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 { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub trigger_source: Option, + #[serde(default)] + pub region: Option, + #[serde(default)] + pub user_pool_id: Option, + pub caller_context: CognitoEventUserPoolsCallerContext, + #[serde(default)] + pub user_name: Option, +} + +/// `CognitoEventUserPoolsPreSignupRequest` contains the request portion of a PreSignup event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignupRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPreSignupResponse` contains the response portion of a PreSignup event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreSignupResponse { + pub auto_confirm_user: bool, + pub auto_verify_email: bool, + pub auto_verify_phone: bool, +} + +/// `CognitoEventUserPoolsPreAuthenticationRequest` contains the request portion of a PreAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreAuthenticationRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, +} + +/// `CognitoEventUserPoolsPreAuthenticationResponse` contains the response portion of a PreAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CognitoEventUserPoolsPreAuthenticationResponse {} +/// `CognitoEventUserPoolsPostConfirmationRequest` contains the request portion of a PostConfirmation event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostConfirmationRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `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")] +pub struct CognitoEventUserPoolsPreTokenGenRequest { + #[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, +} + +/// `CognitoEventUserPoolsPreTokenGenResponse` contains the response portion of a PreTokenGen event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPreTokenGenResponse { + pub claims_override_details: Option, +} + +/// `CognitoEventUserPoolsPostAuthenticationRequest` contains the request portion of a PostAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsPostAuthenticationRequest { + pub new_device_used: bool, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsPostAuthenticationResponse` contains the response portion of a PostAuthentication event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +pub struct CognitoEventUserPoolsPostAuthenticationResponse {} +/// `CognitoEventUserPoolsMigrateUserRequest` contains the request portion of a MigrateUser event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUserRequest { + #[serde(default)] + pub password: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub validation_data: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsMigrateUserResponse` contains the response portion of a MigrateUser event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsMigrateUserResponse { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(default)] + pub final_user_status: Option, + #[serde(default)] + pub message_action: Option, + pub desired_delivery_mediums: Vec, + pub force_alias_creation: bool, +} + +/// `ClaimsOverrideDetails` 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 ClaimsOverrideDetails { + pub group_override_details: GroupConfiguration, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub claims_to_add_or_override: HashMap, + pub claims_to_suppress: Vec, +} + +/// `GroupConfiguration` allows lambda to override groups, roles and set a preferred role +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GroupConfiguration { + pub groups_to_override: Vec, + pub iam_roles_to_override: Vec, + pub preferred_role: Option, +} + +/// `CognitoEventUserPoolsChallengeResult` represents a challenge that is presented to the user in the authentication +/// process that is underway, along with the corresponding result. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsChallengeResult { + #[serde(default)] + pub challenge_name: Option, + pub challenge_result: bool, + #[serde(default)] + pub challenge_metadata: Option, +} + +/// `CognitoEventUserPoolsDefineAuthChallengeRequest` defines auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallengeRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + pub session: Vec>, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, + #[serde(default)] + pub user_not_found: bool, +} + +/// `CognitoEventUserPoolsDefineAuthChallengeResponse` defines auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallengeResponse { + #[serde(default)] + pub challenge_name: Option, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub issue_tokens: bool, + #[serde(default, deserialize_with = "deserialize_nullish_boolean")] + pub fail_authentication: bool, +} + +/// `CognitoEventUserPoolsDefineAuthChallenge` sent by AWS Cognito User Pools to initiate custom authentication flow +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsDefineAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsDefineAuthChallengeRequest, + pub response: CognitoEventUserPoolsDefineAuthChallengeResponse, +} + +/// `CognitoEventUserPoolsCreateAuthChallengeRequest` defines create auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallengeRequest { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(default)] + pub challenge_name: Option, + pub session: Vec>, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsCreateAuthChallengeResponse` defines create auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallengeResponse { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub public_challenge_parameters: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub private_challenge_parameters: HashMap, + #[serde(default)] + pub challenge_metadata: Option, +} + +/// `CognitoEventUserPoolsCreateAuthChallenge` sent by AWS Cognito User Pools to create a challenge to present to the user +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCreateAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsCreateAuthChallengeRequest, + pub response: CognitoEventUserPoolsCreateAuthChallengeResponse, +} + +/// `CognitoEventUserPoolsVerifyAuthChallengeRequest` defines verify auth challenge request parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallengeRequest +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub user_attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub private_challenge_parameters: HashMap, + #[serde(bound = "")] + pub challenge_answer: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsVerifyAuthChallengeResponse` defines verify auth challenge response parameters +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallengeResponse { + #[serde(default)] + pub answer_correct: bool, +} + +/// `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 +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsVerifyAuthChallenge { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsVerifyAuthChallengeRequest, + pub response: CognitoEventUserPoolsVerifyAuthChallengeResponse, +} + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessage { + #[serde(rename = "CognitoEventUserPoolsHeader")] + #[serde(flatten)] + pub cognito_event_user_pools_header: CognitoEventUserPoolsHeader, + pub request: CognitoEventUserPoolsCustomMessageRequest, + pub response: CognitoEventUserPoolsCustomMessageResponse, +} + +/// `CognitoEventUserPoolsCustomMessageRequest` contains the request portion of a CustomMessage event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessageRequest +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub user_attributes: HashMap, + #[serde(default)] + pub code_parameter: Option, + #[serde(default)] + pub username_parameter: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub client_metadata: HashMap, +} + +/// `CognitoEventUserPoolsCustomMessageResponse` contains the response portion of a CustomMessage event +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CognitoEventUserPoolsCustomMessageResponse { + #[serde(default)] + pub sms_message: Option, + #[serde(default)] + pub email_message: Option, + #[serde(default)] + pub email_subject: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event() { + let data = include_bytes!("../../fixtures/example-cognito-event.json"); + let parsed: CognitoEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_create_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-create-auth-challenge.json"); + let parsed: CognitoEventUserPoolsCreateAuthChallenge = serde_json::from_slice(data).unwrap(); + 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() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-custommessage.json"); + let parsed: CognitoEventUserPoolsCustomMessage = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsCustomMessage = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_define_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-define-auth-challenge.json"); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + 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_define_auth_challenge_optional_response_fields() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json" + ); + let parsed: CognitoEventUserPoolsDefineAuthChallenge = serde_json::from_slice(data).unwrap(); + + assert!(!parsed.response.fail_authentication); + assert!(!parsed.response.issue_tokens); + + 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() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-migrateuser.json"); + let parsed: CognitoEventUserPoolsMigrateUser = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsMigrateUser = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_postauthentication() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-postauthentication.json"); + let parsed: CognitoEventUserPoolsPostAuthentication = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPostAuthentication = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_postconfirmation() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-postconfirmation.json"); + let parsed: CognitoEventUserPoolsPostConfirmation = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPostConfirmation = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_preauthentication() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-preauthentication.json"); + let parsed: CognitoEventUserPoolsPreAuthentication = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreAuthentication = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_presignup() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-presignup.json"); + let parsed: CognitoEventUserPoolsPreSignup = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreSignup = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen_incoming() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen-incoming.json"); + let parsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_pretokengen() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-pretokengen.json"); + let parsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: CognitoEventUserPoolsPreTokenGen = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "cognito")] + fn example_cognito_event_userpools_verify_auth_challenge() { + let data = include_bytes!("../../fixtures/example-cognito-event-userpools-verify-auth-challenge.json"); + let parsed: CognitoEventUserPoolsVerifyAuthChallenge = serde_json::from_slice(data).unwrap(); + 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_optional_answer_correct() { + let data = include_bytes!( + "../../fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-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); + } +} diff --git a/lambda-events/src/event/config/mod.rs b/lambda-events/src/event/config/mod.rs new file mode 100644 index 00000000..bb5d0c11 --- /dev/null +++ b/lambda-events/src/event/config/mod.rs @@ -0,0 +1,52 @@ +/// `ConfigEvent` contains data from an event sent from AWS Config +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConfigEvent { + /// The ID of the AWS account that owns the rule + #[serde(default)] + pub account_id: Option, + /// The ARN that AWS Config assigned to the rule + /// + /// nolint:stylecheck + #[serde(default)] + pub config_rule_arn: Option, + /// nolint:stylecheck + #[serde(default)] + pub config_rule_id: Option, + /// The name that you assigned to the rule that caused AWS Config to publish the event + #[serde(default)] + pub config_rule_name: Option, + /// A boolean value that indicates whether the AWS resource to be evaluated has been removed from the rule's scope + pub event_left_scope: bool, + /// nolint:stylecheck + #[serde(default)] + pub execution_role_arn: Option, + /// If the event is published in response to a resource configuration change, this value contains a JSON configuration item + #[serde(default)] + pub invoking_event: Option, + /// A token that the function must pass to AWS Config with the PutEvaluations call + #[serde(default)] + pub result_token: Option, + /// Key/value pairs that the function processes as part of its evaluation logic + #[serde(default)] + pub rule_parameters: Option, + #[serde(default)] + pub version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "config")] + fn example_config_event() { + let data = include_bytes!("../../fixtures/example-config-event.json"); + let parsed: ConfigEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConfigEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/connect/mod.rs b/lambda-events/src/event/connect/mod.rs new file mode 100644 index 00000000..62e86b52 --- /dev/null +++ b/lambda-events/src/event/connect/mod.rs @@ -0,0 +1,116 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +/// `ConnectEvent` contains the data structure for a Connect event. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectEvent { + #[serde(rename = "Details")] + pub details: ConnectDetails, + /// The name of the event. + #[serde(default)] + #[serde(rename = "Name")] + pub name: Option, +} + +/// `ConnectDetails` holds the details of a Connect event +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectDetails { + #[serde(rename = "ContactData")] + pub contact_data: ConnectContactData, + /// The parameters that have been set in the Connect instance at the time of the Lambda invocation. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "Parameters")] + pub parameters: HashMap, +} + +/// `ConnectContactData` holds all of the contact information for the user that invoked the Connect event. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectContactData { + /// The custom attributes from Connect that the Lambda function was invoked with. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "Attributes")] + pub attributes: HashMap, + #[serde(default)] + #[serde(rename = "Channel")] + pub channel: Option, + #[serde(default)] + #[serde(rename = "ContactId")] + pub contact_id: Option, + #[serde(rename = "CustomerEndpoint")] + pub customer_endpoint: ConnectEndpoint, + #[serde(default)] + #[serde(rename = "InitialContactId")] + pub initial_contact_id: Option, + /// Either: INBOUND/OUTBOUND/TRANSFER/CALLBACK + #[serde(default)] + #[serde(rename = "InitiationMethod")] + pub initiation_method: Option, + #[serde(default)] + #[serde(rename = "PreviousContactId")] + pub previous_contact_id: Option, + #[serde(rename = "Queue", default)] + pub queue: Option, + #[serde(rename = "SystemEndpoint")] + pub system_endpoint: ConnectEndpoint, + #[serde(default)] + #[serde(rename = "InstanceARN")] + pub instance_arn: Option, +} + +/// `ConnectEndpoint` represents routing information. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectEndpoint { + #[serde(default)] + #[serde(rename = "Address")] + pub address: Option, + #[serde(default)] + #[serde(rename = "Type")] + pub type_: Option, +} + +/// `ConnectQueue` represents a queue object. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConnectQueue { + #[serde(default)] + #[serde(rename = "Name")] + pub name: Option, + #[serde(default)] + #[serde(rename = "ARN")] + pub arn: Option, +} + +pub type ConnectResponse = HashMap; + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "connect")] + fn example_connect_event() { + let data = include_bytes!("../../fixtures/example-connect-event.json"); + let parsed: ConnectEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConnectEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "connect")] + fn example_connect_event_without_queue() { + let data = include_bytes!("../../fixtures/example-connect-event-without-queue.json"); + let parsed: ConnectEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: ConnectEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/dynamodb/attributes.rs b/lambda-events/src/event/dynamodb/attributes.rs new file mode 100644 index 00000000..74449fe8 --- /dev/null +++ b/lambda-events/src/event/dynamodb/attributes.rs @@ -0,0 +1,191 @@ +use event::serde_dynamo::AttributeValue; +use std::collections::HashMap; + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_null_attribute() { + let value = serde_json::json!({ + "NULL": true + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Null(true) => {} + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_string_attribute() { + let value = serde_json::json!({ + "S": "value" + }); + + 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), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_number_attribute() { + let value = serde_json::json!({ + "N": "123.45" + }); + + 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), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_binary_attribute() { + let value = serde_json::json!({ + "B": "dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk" + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::B(ref b) => { + let expected = base64::decode("dGhpcyB0ZXh0IGlzIGJhc2U2NC1lbmNvZGVk").unwrap(); + assert_eq!(&expected, b) + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_boolean_attribute() { + let value = serde_json::json!({ + "BOOL": true + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Bool(b) => assert_eq!(true, b), + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_string_set_attribute() { + let value = serde_json::json!({ + "SS": ["Giraffe", "Hippo" ,"Zebra"] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Ss(ref s) => { + let expected = vec!["Giraffe", "Hippo", "Zebra"]; + assert_eq!(expected, s.iter().collect::>()); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_number_set_attribute() { + let value = serde_json::json!({ + "NS": ["42.2", "-19", "7.5", "3.14"] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Ns(ref s) => { + let expected = vec!["42.2", "-19", "7.5", "3.14"]; + assert_eq!(expected, s.iter().collect::>()); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_binary_set_attribute() { + let value = serde_json::json!({ + "BS": ["U3Vubnk=", "UmFpbnk=", "U25vd3k="] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::Bs(ref s) => { + let expected = vec!["U3Vubnk=", "UmFpbnk=", "U25vd3k="] + .into_iter() + .flat_map(base64::decode) + .collect::>(); + assert_eq!(&expected, s); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_attribute_list_attribute() { + let value = serde_json::json!({ + "L": [ {"S": "Cookies"} , {"S": "Coffee"}, {"N": "3.14159"}] + }); + + let attr: AttributeValue = serde_json::from_value(value.clone()).unwrap(); + match attr { + AttributeValue::L(ref s) => { + let expected = vec![ + AttributeValue::S("Cookies".into()), + AttributeValue::S("Coffee".into()), + AttributeValue::N("3.14159".into()), + ]; + assert_eq!(&expected, s); + } + other => panic!("unexpected value {:?}", other), + } + + let reparsed = serde_json::to_value(attr).unwrap(); + assert_eq!(value, reparsed); + } + + #[test] + fn test_attribute_map_attribute() { + let value = serde_json::json!({ + "M": {"Name": {"S": "Joe"}, "Age": {"N": "35"}} + }); + + let attr: AttributeValue = serde_json::from_value(value).unwrap(); + match attr { + AttributeValue::M(s) => { + let mut expected = HashMap::new(); + expected.insert("Name".into(), AttributeValue::S("Joe".into())); + expected.insert("Age".into(), AttributeValue::N("35".into())); + assert_eq!(expected, s); + } + other => panic!("unexpected value {:?}", other), + } + } +} diff --git a/lambda-events/src/event/dynamodb/mod.rs b/lambda-events/src/event/dynamodb/mod.rs new file mode 100644 index 00000000..00ff08e4 --- /dev/null +++ b/lambda-events/src/event/dynamodb/mod.rs @@ -0,0 +1,280 @@ +use crate::custom_serde::*; +use crate::streams::DynamoDbBatchItemFailure; +use crate::time_window::*; +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; +use std::fmt; + +#[cfg(test)] +mod attributes; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum StreamViewType { + NewImage, + OldImage, + NewAndOldImages, + KeysOnly, +} + +impl fmt::Display for StreamViewType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + StreamViewType::NewImage => "NEW_IMAGE", + StreamViewType::OldImage => "OLD_IMAGE", + StreamViewType::NewAndOldImages => "NEW_AND_OLD_IMAGES", + StreamViewType::KeysOnly => "KEYS_ONLY", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum StreamStatus { + Enabling, + Enabled, + Disabling, + Disabled, +} + +impl fmt::Display for StreamStatus { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + StreamStatus::Enabling => "ENABLING", + StreamStatus::Enabled => "ENABLED", + StreamStatus::Disabling => "DISABLING", + StreamStatus::Disabled => "DISABLED", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum SharedIteratorType { + TrimHorizon, + Latest, + AtSequenceNumber, + AfterSequenceNumber, +} + +impl fmt::Display for SharedIteratorType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + SharedIteratorType::TrimHorizon => "TRIM_HORIZON", + SharedIteratorType::Latest => "LATEST", + SharedIteratorType::AtSequenceNumber => "AT_SEQUENCE_NUMBER", + SharedIteratorType::AfterSequenceNumber => "AFTER_SEQUENCE_NUMBER", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum OperationType { + Insert, + Modify, + Remove, +} + +impl fmt::Display for OperationType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + OperationType::Insert => "INSERT", + OperationType::Modify => "MODIFY", + OperationType::Remove => "REMOVE", + }; + write!(f, "{}", val) + } +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum KeyType { + Hash, + Range, +} + +impl fmt::Display for KeyType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let val = match self { + KeyType::Hash => "HASH", + KeyType::Range => "RANGE", + }; + write!(f, "{}", val) + } +} + +/// 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")] + pub records: Vec, +} + +/// `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 +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEvent { + #[serde(rename = "DynamoDBEvent")] + #[serde(flatten)] + pub dynamo_db_event: Event, + #[serde(rename = "TimeWindowProperties")] + #[serde(flatten)] + pub time_window_properties: TimeWindowProperties, +} + +/// `TimeWindowEventResponse` is the outer structure to report batch item failures for DynamoDBTimeWindowEvent. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEventResponse { + #[serde(rename = "TimeWindowEventResponseProperties")] + #[serde(flatten)] + pub time_window_event_response_properties: TimeWindowEventResponseProperties, + pub batch_item_failures: Vec, +} + +/// EventRecord stores information about each record of a DynamoDb stream event +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EventRecord { + /// The region in which the GetRecords request was received. + pub aws_region: String, + /// The main body of the stream record, containing all of the DynamoDB-specific + /// fields. + #[serde(rename = "dynamodb")] + pub change: StreamRecord, + /// A globally unique identifier for the event that was recorded in this stream + /// record. + #[serde(rename = "eventID")] + pub event_id: String, + /// The type of data modification that was performed on the DynamoDB table: + /// + /// * INSERT - a new item was added to the table. + /// + /// * MODIFY - one or more of an existing item's attributes were modified. + /// + /// * REMOVE - the item was deleted from the table + pub event_name: String, + /// The AWS service from which the stream record originated. For DynamoDB Streams, + /// this is aws:dynamodb. + #[serde(default)] + pub event_source: Option, + /// The version number of the stream record format. This number is updated whenever + /// the structure of Record is modified. + /// + /// Client applications must not assume that eventVersion will remain at a particular + /// value, as this number is subject to change at any time. In general, eventVersion + /// will only increase as the low-level DynamoDB Streams API evolves. + #[serde(default)] + pub event_version: Option, + /// The event source ARN of DynamoDB + #[serde(rename = "eventSourceARN")] + #[serde(default)] + pub event_source_arn: Option, + /// Items that are deleted by the Time to Live process after expiration have + /// the following fields: + /// + /// * Records[].userIdentity.type + /// + /// "Service" + /// + /// * Records[].userIdentity.principalId + /// + /// "dynamodb.amazonaws.com" + #[serde(default)] + pub user_identity: Option, + /// Describes the record format and relevant mapping information that + /// should be applied to schematize the records on the stream. For + /// DynamoDB Streams, this is application/json. + #[serde(default)] + pub record_format: Option, + /// The DynamoDB table that this event was recorded for. + #[serde(default)] + pub table_name: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserIdentity { + #[serde(default)] + pub type_: String, + #[serde(default)] + pub principal_id: String, +} + +/// `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)] +#[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. + #[serde(rename = "ApproximateCreationDateTime")] + #[serde(with = "float_unix_epoch")] + 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")] + #[serde(default)] + #[serde(rename = "Keys")] + pub keys: serde_dynamo::Item, + /// The item in the DynamoDB table as it appeared after it was modified. + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + #[serde(default)] + #[serde(rename = "NewImage")] + pub new_image: serde_dynamo::Item, + /// The item in the DynamoDB table as it appeared before it was modified. + #[serde(deserialize_with = "deserialize_lambda_dynamodb_item")] + #[serde(default)] + #[serde(rename = "OldImage")] + pub old_image: serde_dynamo::Item, + /// The sequence number of the stream record. + #[serde(default)] + #[serde(rename = "SequenceNumber")] + pub sequence_number: Option, + /// The size of the stream record, in bytes. + #[serde(rename = "SizeBytes")] + pub size_bytes: i64, + /// The type of data from the modified DynamoDB item that was captured in this + /// stream record. + #[serde(default)] + #[serde(rename = "StreamViewType")] + pub stream_view_type: Option, +} + +#[cfg(test)] +#[allow(deprecated)] +mod test { + use super::*; + use chrono::TimeZone; + + extern crate serde_json; + + #[test] + #[cfg(feature = "dynamodb")] + fn example_dynamodb_event() { + let data = include_bytes!("../../fixtures/example-dynamodb-event.json"); + let mut 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); + + let event = parsed.records.pop().unwrap(); + let date = Utc.ymd(2016, 12, 2).and_hms(1, 27, 0); + assert_eq!(date, event.change.approximate_creation_date_time); + } + + #[test] + #[cfg(feature = "dynamodb")] + fn example_dynamodb_event_with_optional_fields() { + let data = include_bytes!("../../fixtures/example-dynamodb-event-record-with-optional-fields.json"); + let parsed: EventRecord = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: EventRecord = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/ecr_scan/mod.rs b/lambda-events/src/event/ecr_scan/mod.rs new file mode 100644 index 00000000..87dede6f --- /dev/null +++ b/lambda-events/src/event/ecr_scan/mod.rs @@ -0,0 +1,73 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEvent { + #[serde(default)] + pub version: Option, + #[serde(default)] + pub id: Option, + #[serde(default)] + #[serde(rename = "detail-type")] + pub detail_type: Option, + #[serde(default)] + pub source: Option, + #[serde(default)] + pub time: Option, + #[serde(default)] + pub region: Option, + pub resources: Vec, + #[serde(default)] + pub account: Option, + pub detail: EcrScanEventDetailType, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEventDetailType { + #[serde(default)] + #[serde(rename = "scan-status")] + pub scan_status: Option, + #[serde(default)] + #[serde(rename = "repository-name")] + pub repository_name: Option, + #[serde(rename = "finding-severity-counts")] + pub finding_severity_counts: EcrScanEventFindingSeverityCounts, + #[serde(default)] + #[serde(rename = "image-digest")] + pub image_digest: Option, + #[serde(rename = "image-tags")] + pub image_tags: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct EcrScanEventFindingSeverityCounts { + #[serde(rename = "CRITICAL")] + pub critical: i64, + #[serde(rename = "HIGH")] + pub high: i64, + #[serde(rename = "MEDIUM")] + pub medium: i64, + #[serde(rename = "LOW")] + pub low: i64, + #[serde(rename = "INFORMATIONAL")] + pub informational: i64, + #[serde(rename = "UNDEFINED")] + pub undefined: i64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "ecr_scan")] + fn example_ecr_image_scan_event() { + let data = include_bytes!("../../fixtures/example-ecr-image-scan-event.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/event/firehose/mod.rs b/lambda-events/src/event/firehose/mod.rs new file mode 100644 index 00000000..63ef3e1f --- /dev/null +++ b/lambda-events/src/event/firehose/mod.rs @@ -0,0 +1,87 @@ +use crate::custom_serde::*; +use crate::encodings::{Base64Data, MillisecondTimestamp}; +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)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseEvent { + #[serde(default)] + pub invocation_id: Option, + /// nolint: stylecheck + #[serde(default)] + pub delivery_stream_arn: Option, + /// nolint: stylecheck + #[serde(default)] + pub source_kinesis_stream_arn: Option, + #[serde(default)] + pub region: Option, + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseEventRecord { + #[serde(default)] + pub record_id: Option, + pub approximate_arrival_timestamp: MillisecondTimestamp, + pub data: Base64Data, + #[serde(rename = "kinesisRecordMetadata")] + pub kinesis_firehose_record_metadata: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponse { + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponseRecord { + #[serde(default)] + pub record_id: Option, + /// The status of the transformation. May be TransformedStateOk, TransformedStateDropped or TransformedStateProcessingFailed + #[serde(default)] + pub result: Option, + pub data: Base64Data, + pub metadata: KinesisFirehoseResponseRecordMetadata, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseResponseRecordMetadata { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub partition_keys: HashMap, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisFirehoseRecordMetadata { + #[serde(default)] + pub shard_id: Option, + #[serde(default)] + pub partition_key: Option, + #[serde(default)] + pub sequence_number: Option, + pub subsequence_number: i64, + pub approximate_arrival_timestamp: MillisecondTimestamp, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "firehose")] + fn example_firehose_event() { + let data = include_bytes!("../../fixtures/example-firehose-event.json"); + let parsed: KinesisFirehoseEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KinesisFirehoseEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iam/mod.rs b/lambda-events/src/event/iam/mod.rs new file mode 100644 index 00000000..1b73e44b --- /dev/null +++ b/lambda-events/src/event/iam/mod.rs @@ -0,0 +1,23 @@ +/// `IamPolicyDocument` represents an IAM policy document. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +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")] +pub struct IamPolicyStatement { + #[serde(rename = "Action")] + pub action: Vec, + #[serde(default)] + #[serde(rename = "Effect")] + pub effect: Option, + #[serde(rename = "Resource")] + pub resource: Vec, +} diff --git a/lambda-events/src/event/iot/mod.rs b/lambda-events/src/event/iot/mod.rs new file mode 100644 index 00000000..9f45899f --- /dev/null +++ b/lambda-events/src/event/iot/mod.rs @@ -0,0 +1,99 @@ +use crate::custom_serde::*; +use crate::encodings::Base64Data; +use crate::iam::IamPolicyDocument; +use http::HeaderMap; + +/// `IoTCoreCustomAuthorizerRequest` represents the request to an IoT Core custom authorizer. +/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreCustomAuthorizerRequest { + #[serde(default)] + pub token: Option, + pub signature_verified: bool, + pub protocols: Vec, + pub protocol_data: Option, + pub connection_metadata: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreProtocolData { + pub tls: Option, + pub http: Option, + pub mqtt: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreTlsContext { + #[serde(default)] + pub server_name: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreHttpContext { + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(default)] + pub query_string: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreMqttContext { + #[serde(default)] + pub client_id: Option, + pub password: Base64Data, + #[serde(default)] + pub username: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreConnectionMetadata { + #[serde(default)] + pub id: Option, +} + +/// `IoTCoreCustomAuthorizerResponse` represents the response from an IoT Core custom authorizer. +/// See https://docs.aws.amazon.com/iot/latest/developerguide/config-custom-auth.html +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTCoreCustomAuthorizerResponse { + pub is_authenticated: bool, + #[serde(default)] + pub principal_id: Option, + pub disconnect_after_in_seconds: u32, + pub refresh_after_in_seconds: u32, + pub policy_documents: Vec>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot")] + fn example_iot_custom_auth_request() { + let data = include_bytes!("../../fixtures/example-iot-custom-auth-request.json"); + let parsed: IoTCoreCustomAuthorizerRequest = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTCoreCustomAuthorizerRequest = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "iot")] + fn example_iot_custom_auth_response() { + let data = include_bytes!("../../fixtures/example-iot-custom-auth-response.json"); + let parsed: IoTCoreCustomAuthorizerResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTCoreCustomAuthorizerResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_1_click/mod.rs b/lambda-events/src/event/iot_1_click/mod.rs new file mode 100644 index 00000000..0e1c11b6 --- /dev/null +++ b/lambda-events/src/event/iot_1_click/mod.rs @@ -0,0 +1,72 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +/// `IoTOneClickEvent` represents a click event published by clicking button type +/// device. +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickEvent { + pub device_event: IoTOneClickDeviceEvent, + pub device_info: IoTOneClickDeviceInfo, + pub placement_info: IoTOneClickPlacementInfo, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickDeviceEvent { + pub button_clicked: IoTOneClickButtonClicked, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickButtonClicked { + #[serde(default)] + pub click_type: Option, + #[serde(default)] + pub reported_time: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickDeviceInfo { + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(default)] + pub type_: Option, + #[serde(default)] + pub device_id: Option, + pub remaining_life: f64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTOneClickPlacementInfo { + #[serde(default)] + pub project_name: Option, + #[serde(default)] + pub placement_name: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub attributes: HashMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub devices: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot_1_click")] + fn example_iot_1_click_event() { + let data = include_bytes!("../../fixtures/example-iot_1_click-event.json"); + let parsed: IoTOneClickEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTOneClickEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_button/mod.rs b/lambda-events/src/event/iot_button/mod.rs new file mode 100644 index 00000000..32ba8d5a --- /dev/null +++ b/lambda-events/src/event/iot_button/mod.rs @@ -0,0 +1,27 @@ +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IoTButtonEvent { + #[serde(default)] + pub serial_number: Option, + #[serde(default)] + pub click_type: Option, + #[serde(default)] + pub battery_voltage: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "iot_button")] + fn example_iot_button_event() { + let data = include_bytes!("../../fixtures/example-iot_button-event.json"); + let parsed: IoTButtonEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: IoTButtonEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/iot_deprecated/mod.rs b/lambda-events/src/event/iot_deprecated/mod.rs new file mode 100644 index 00000000..4304d7cd --- /dev/null +++ b/lambda-events/src/event/iot_deprecated/mod.rs @@ -0,0 +1,35 @@ +use crate::iot::*; + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct IoTCustomAuthorizerRequest { + pub http_context: Option, + pub mqtt_context: Option, + pub tls_context: Option, + #[serde(default)] + #[serde(rename = "token")] + pub authorization_token: Option, + #[serde(default)] + pub token_signature: Option, +} + +pub type IoTHttpContext = IoTCoreHttpContext; + +pub type IoTMqttContext = IoTCoreMqttContext; + +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)] +#[serde(rename_all = "camelCase")] +pub struct IoTCustomAuthorizerResponse { + pub is_authenticated: bool, + #[serde(default)] + pub principal_id: Option, + pub disconnect_after_in_seconds: i32, + pub refresh_after_in_seconds: i32, + pub policy_documents: Vec, +} diff --git a/lambda-events/src/event/kafka/mod.rs b/lambda-events/src/event/kafka/mod.rs new file mode 100644 index 00000000..6c4d78fa --- /dev/null +++ b/lambda-events/src/event/kafka/mod.rs @@ -0,0 +1,49 @@ +use crate::custom_serde::*; +use crate::encodings::MillisecondTimestamp; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub records: HashMap>, + #[serde(default)] + pub bootstrap_servers: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KafkaRecord { + #[serde(default)] + pub topic: Option, + pub partition: i64, + pub offset: i64, + pub timestamp: MillisecondTimestamp, + #[serde(default)] + pub timestamp_type: Option, + pub key: Option, + pub value: Option, + pub headers: Vec>>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "kafka")] + fn example_kafka_event() { + let data = include_bytes!("../../fixtures/example-kafka-event.json"); + let parsed: KafkaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: KafkaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/kinesis/analytics.rs b/lambda-events/src/event/kinesis/analytics.rs new file mode 100644 index 00000000..1704009e --- /dev/null +++ b/lambda-events/src/event/kinesis/analytics.rs @@ -0,0 +1,35 @@ +use crate::encodings::Base64Data; + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryEvent { + #[serde(default)] + pub invocation_id: Option, + #[serde(default)] + pub application_arn: Option, + pub records: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryEventRecord { + #[serde(default)] + pub record_id: Option, + pub data: Base64Data, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryResponse { + pub records: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisAnalyticsOutputDeliveryResponseRecord { + #[serde(default)] + pub record_id: Option, + /// possible values include Ok and DeliveryFailed + #[serde(default)] + pub result: Option, +} diff --git a/lambda-events/src/event/kinesis/event.rs b/lambda-events/src/event/kinesis/event.rs new file mode 100644 index 00000000..c401fa72 --- /dev/null +++ b/lambda-events/src/event/kinesis/event.rs @@ -0,0 +1,88 @@ +use crate::encodings::{Base64Data, SecondTimestamp}; +use crate::time_window::{TimeWindowEventResponseProperties, TimeWindowProperties}; + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// `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 +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisTimeWindowEvent { + #[serde(rename = "KinesisEvent")] + #[serde(flatten)] + pub kinesis_event: KinesisEvent, + #[serde(rename = "TimeWindowProperties")] + #[serde(flatten)] + pub time_window_properties: TimeWindowProperties, +} + +/// `KinesisTimeWindowEventResponse` is the outer structure to report batch item failures for KinesisTimeWindowEvent. +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisTimeWindowEventResponse { + #[serde(rename = "TimeWindowEventResponseProperties")] + #[serde(flatten)] + pub time_window_event_response_properties: TimeWindowEventResponseProperties, + // pub batch_item_failures: Vec, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisEventRecord { + /// nolint: stylecheck + #[serde(default)] + pub aws_region: Option, + #[serde(default)] + #[serde(rename = "eventID")] + pub event_id: Option, + #[serde(default)] + pub event_name: Option, + #[serde(default)] + pub event_source: Option, + /// nolint: stylecheck + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_version: Option, + /// nolint: stylecheck + #[serde(default)] + pub invoke_identity_arn: Option, + pub kinesis: KinesisRecord, +} + +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisRecord { + pub approximate_arrival_timestamp: SecondTimestamp, + pub data: Base64Data, + pub encryption_type: Option, + #[serde(default)] + pub partition_key: Option, + #[serde(default)] + pub sequence_number: Option, + #[serde(default)] + pub kinesis_schema_version: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "kinesis")] + fn example_kinesis_event() { + let data = include_bytes!("../../fixtures/example-kinesis-event.json"); + let parsed: KinesisEvent = serde_json::from_slice(data).unwrap(); + 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/event/kinesis/mod.rs b/lambda-events/src/event/kinesis/mod.rs new file mode 100644 index 00000000..079280b9 --- /dev/null +++ b/lambda-events/src/event/kinesis/mod.rs @@ -0,0 +1,3 @@ +pub mod analytics; +mod event; +pub use self::event::*; diff --git a/lambda-events/src/event/lambda_function_urls/mod.rs b/lambda-events/src/event/lambda_function_urls/mod.rs new file mode 100644 index 00000000..d1567b56 --- /dev/null +++ b/lambda-events/src/event/lambda_function_urls/mod.rs @@ -0,0 +1,104 @@ +use crate::custom_serde::*; +use http::HeaderMap; +use std::collections::HashMap; + +/// `LambdaFunctionUrlRequest` contains data coming from the HTTP request to a Lambda Function URL. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequest { + /// Version is expected to be `"2.0"` + #[serde(default)] + pub version: Option, + #[serde(default)] + pub raw_path: Option, + #[serde(default)] + pub raw_query_string: Option, + pub cookies: Option>, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub query_string_parameters: HashMap, + pub request_context: LambdaFunctionUrlRequestContext, + pub body: Option, + pub is_base64_encoded: bool, +} + +/// `LambdaFunctionUrlRequestContext` contains the information to identify the AWS account and resources invoking the Lambda function. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContext { + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub request_id: Option, + pub authorizer: Option, + /// APIID is the Lambda URL ID + #[serde(default)] + #[serde(rename = "apiId")] + pub apiid: Option, + /// DomainName is of the format `".lambda-url..on.aws"` + #[serde(default)] + pub domain_name: Option, + /// DomainPrefix is the Lambda URL ID + #[serde(default)] + pub domain_prefix: Option, + #[serde(default)] + pub time: Option, + pub time_epoch: i64, + pub http: LambdaFunctionUrlRequestContextHttpDescription, +} + +/// `LambdaFunctionUrlRequestContextAuthorizerDescription` contains authorizer information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextAuthorizerDescription { + pub iam: Option, +} + +/// `LambdaFunctionUrlRequestContextAuthorizerIamDescription` contains IAM information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextAuthorizerIamDescription { + #[serde(default)] + pub access_key: Option, + #[serde(default)] + pub account_id: Option, + #[serde(default)] + pub caller_id: Option, + #[serde(default)] + pub user_arn: Option, + #[serde(default)] + pub user_id: Option, +} + +/// `LambdaFunctionUrlRequestContextHttpDescription` contains HTTP information for the request context. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlRequestContextHttpDescription { + #[serde(default)] + pub method: Option, + #[serde(default)] + pub path: Option, + #[serde(default)] + pub protocol: Option, + #[serde(default)] + pub source_ip: Option, + #[serde(default)] + pub user_agent: Option, +} + +/// `LambdaFunctionUrlResponse` configures the HTTP response to be returned by Lambda Function URL for the request. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LambdaFunctionUrlResponse { + pub status_code: i64, + #[serde(deserialize_with = "http_serde::header_map::deserialize", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, + #[serde(default)] + pub body: Option, + pub is_base64_encoded: bool, + pub cookies: Vec, +} diff --git a/lambda-events/src/event/lex/mod.rs b/lambda-events/src/event/lex/mod.rs new file mode 100644 index 00000000..a3593dfd --- /dev/null +++ b/lambda-events/src/event/lex/mod.rs @@ -0,0 +1,130 @@ +use crate::custom_serde::*; +use std::collections::HashMap; + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexEvent { + pub message_version: Option, + pub invocation_source: Option, + pub user_id: Option, + pub input_transcript: Option, + pub session_attributes: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub request_attributes: HashMap, + pub bot: Option, + pub output_dialog_mode: Option, + pub current_intent: Option, + pub alternative_intents: Option>, + /// Deprecated: the DialogAction field is never populated by Lex events + pub dialog_action: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexBot { + pub name: Option, + pub alias: Option, + pub version: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexCurrentIntent { + pub name: Option, + pub nlu_intent_confidence_score: Option, + pub slots: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub slot_details: HashMap, + pub confirmation_status: Option, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexAlternativeIntents { + pub name: Option, + pub nlu_intent_confidence_score: Option, + pub slots: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub slot_details: HashMap, + pub confirmation_status: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SlotDetail { + pub resolutions: Option>>, + pub original_value: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexDialogAction { + pub type_: Option, + pub fulfillment_state: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message: HashMap, + pub intent_name: Option, + pub slots: Option, + pub slot_to_elicit: Option, + pub response_card: Option, +} + +pub type SessionAttributes = HashMap; + +pub type Slots = HashMap>; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexResponse { + pub session_attributes: SessionAttributes, + pub dialog_action: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LexResponseCard { + pub version: Option, + pub content_type: Option, + pub generic_attachments: Option>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Attachment { + pub title: Option, + pub sub_title: Option, + pub image_url: Option, + pub attachment_link_url: Option, + pub buttons: Option>>, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "lex")] + fn example_lex_event() { + let data = include_bytes!("../../fixtures/example-lex-event.json"); + let parsed: LexEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LexEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "lex")] + fn example_lex_response() { + let data = include_bytes!("../../fixtures/example-lex-response.json"); + let parsed: LexEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: LexEvent = 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 new file mode 100644 index 00000000..e7b8c7f7 --- /dev/null +++ b/lambda-events/src/event/mod.rs @@ -0,0 +1,140 @@ +/// AWS Lambda event definitions for activemq. +#[cfg(feature = "activemq")] +pub mod activemq; + +/// AWS Lambda event definitions for alb. +#[cfg(feature = "alb")] +pub mod alb; +/// AWS Lambda event definitions for apigw. +#[cfg(feature = "apigw")] +pub mod apigw; + +/// AWS Lambda event definitions for appsync. +#[cfg(feature = "appsync")] +pub mod appsync; + +/// AWS Lambda event definitions for autoscaling. +#[cfg(feature = "autoscaling")] +pub mod autoscaling; + +/// AWS Lambda event definitions for chime_bot. +#[cfg(feature = "chime_bot")] +pub mod chime_bot; + +/// AWS Lambda event definitions for clientvpn. +#[cfg(feature = "clientvpn")] +pub mod clientvpn; + +/// CloudWatch Events payload +#[cfg(feature = "cloudwatch_events")] +pub mod cloudwatch_events; + +/// AWS Lambda event definitions for cloudwatch_logs. +#[cfg(feature = "cloudwatch_logs")] +pub mod cloudwatch_logs; + +/// AWS Lambda event definitions for code_commit. +#[cfg(feature = "code_commit")] +pub mod code_commit; + +/// AWS Lambda event definitions for codebuild. +#[cfg(feature = "codebuild")] +pub mod codebuild; + +/// AWS Lambda event definitions for codedeploy. +#[cfg(feature = "codedeploy")] +pub mod codedeploy; + +/// AWS Lambda event definitions for codepipeline_cloudwatch. +#[cfg(feature = "codepipeline_cloudwatch")] +pub mod codepipeline_cloudwatch; + +/// AWS Lambda event definitions for codepipeline_job. +#[cfg(feature = "codepipeline_job")] +pub mod codepipeline_job; + +/// AWS Lambda event definitions for cognito. +#[cfg(feature = "cognito")] +pub mod cognito; + +/// AWS Lambda event definitions for config. +#[cfg(feature = "config")] +pub mod config; + +/// AWS Lambda event definitions for connect. +#[cfg(feature = "connect")] +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. +#[cfg(feature = "ecr_scan")] +pub mod ecr_scan; + +/// AWS Lambda event definitions for firehose. +#[cfg(feature = "firehose")] +pub mod firehose; + +/// AWS Lambda event definitions for iam. +#[cfg(feature = "iam")] +pub mod iam; + +/// AWS Lambda event definitions for iot. +#[cfg(feature = "iot")] +pub mod iot; + +/// AWS Lambda event definitions for iot_1_click. +#[cfg(feature = "iot_1_click")] +pub mod iot_1_click; + +/// AWS Lambda event definitions for iot_button. +#[cfg(feature = "iot_button")] +pub mod iot_button; + +/// AWS Lambda event definitions for iot_deprecated. +#[cfg(feature = "iot_deprecated")] +pub mod iot_deprecated; + +/// AWS Lambda event definitions for kafka. +#[cfg(feature = "kafka")] +pub mod kafka; + +/// AWS Lambda event definitions for kinesis. +#[cfg(feature = "kinesis")] +pub mod kinesis; + +/// AWS Lambda event definitions for lambda_function_urls. +#[cfg(feature = "lambda_function_urls")] +pub mod lambda_function_urls; + +/// AWS Lambda event definitions for lex. +#[cfg(feature = "lex")] +pub mod lex; + +/// AWS Lambda event definitions for rabbitmq. +#[cfg(feature = "rabbitmq")] +pub mod rabbitmq; + +/// AWS Lambda event definitions for s3. +#[cfg(feature = "s3")] +pub mod s3; + +/// AWS Lambda event definitions for ses. +#[cfg(feature = "ses")] +pub mod ses; + +/// AWS Lambda event definitions for SNS. +#[cfg(feature = "sns")] +pub mod sns; + +/// AWS Lambda event definitions for SQS. +#[cfg(feature = "sqs")] +pub mod sqs; + +/// AWS Lambda event definitions for streams. +#[cfg(feature = "streams")] +pub mod streams; diff --git a/lambda-events/src/event/rabbitmq/mod.rs b/lambda-events/src/event/rabbitmq/mod.rs new file mode 100644 index 00000000..c8af802f --- /dev/null +++ b/lambda-events/src/event/rabbitmq/mod.rs @@ -0,0 +1,76 @@ +use crate::custom_serde::*; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqEvent { + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub event_source_arn: Option, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(rename = "rmqMessagesByQueue")] + pub messages_by_queue: HashMap>, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqMessage { + pub basic_properties: RabbitMqBasicProperties, + #[serde(default)] + pub data: Option, + pub redelivered: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RabbitMqBasicProperties +where + T1: DeserializeOwned, + T1: Serialize, +{ + #[serde(default)] + pub content_type: Option, + pub content_encoding: Option, + /// Application or header exchange table + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + #[serde(bound = "")] + pub headers: HashMap, + pub delivery_mode: u8, + pub priority: u8, + pub correlation_id: Option, + pub reply_to: Option, + #[serde(default)] + pub expiration: Option, + pub message_id: Option, + #[serde(default)] + pub timestamp: Option, + pub type_: Option, + #[serde(default)] + pub user_id: Option, + pub app_id: Option, + pub cluster_id: Option, + pub body_size: u64, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "rabbitmq")] + fn example_rabbitmq_event() { + let data = include_bytes!("../../fixtures/example-rabbitmq-event.json"); + let parsed: RabbitMqEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: RabbitMqEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/s3/batch_job.rs b/lambda-events/src/event/s3/batch_job.rs new file mode 100644 index 00000000..8db71896 --- /dev/null +++ b/lambda-events/src/event/s3/batch_job.rs @@ -0,0 +1,58 @@ +/// `S3BatchJobEvent` encapsulates the detail of a s3 batch job +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobEvent { + #[serde(default)] + pub invocation_schema_version: Option, + #[serde(default)] + pub invocation_id: Option, + pub job: S3BatchJob, + pub tasks: Vec, +} + +/// `S3BatchJob` whichs have the job id +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJob { + #[serde(default)] + pub id: Option, +} + +/// `S3BatchJobTask` represents one task in the s3 batch job and have all task details +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobTask { + #[serde(default)] + pub task_id: Option, + #[serde(default)] + pub s3_key: Option, + #[serde(default)] + pub s3_version_id: Option, + #[serde(default)] + pub s3_bucket_arn: Option, +} + +/// `S3BatchJobResponse` is the response of a iven s3 batch job with the results +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobResponse { + #[serde(default)] + pub invocation_schema_version: Option, + #[serde(default)] + pub treat_missing_keys_as: Option, + #[serde(default)] + pub invocation_id: Option, + pub results: Vec, +} + +/// `S3BatchJobResult` represents the result of a given task +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3BatchJobResult { + #[serde(default)] + pub task_id: Option, + #[serde(default)] + pub result_code: Option, + #[serde(default)] + pub result_string: Option, +} diff --git a/lambda-events/src/event/s3/event.rs b/lambda-events/src/event/s3/event.rs new file mode 100644 index 00000000..b25cfdd2 --- /dev/null +++ b/lambda-events/src/event/s3/event.rs @@ -0,0 +1,114 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use std::collections::HashMap; + +/// `S3Event` which wrap an array of `S3Event`Record +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Event { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// `S3EventRecord` which wrap record data +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3EventRecord { + #[serde(default)] + pub event_version: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, + pub event_time: DateTime, + #[serde(default)] + pub event_name: Option, + #[serde(rename = "userIdentity")] + pub principal_id: S3UserIdentity, + pub request_parameters: S3RequestParameters, + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub response_elements: HashMap, + pub s3: S3Entity, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3UserIdentity { + #[serde(default)] + pub principal_id: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3RequestParameters { + #[serde(default)] + #[serde(rename = "sourceIPAddress")] + pub source_ip_address: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Entity { + #[serde(default)] + #[serde(rename = "s3SchemaVersion")] + pub schema_version: Option, + #[serde(default)] + pub configuration_id: Option, + pub bucket: S3Bucket, + pub object: S3Object, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Bucket { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub owner_identity: Option, + #[serde(default)] + pub arn: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct S3Object { + #[serde(default)] + pub key: Option, + pub size: Option, + #[serde(default)] + pub url_decoded_key: Option, + #[serde(default)] + pub version_id: Option, + #[serde(default)] + pub e_tag: Option, + #[serde(default)] + pub sequencer: Option, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "s3")] + fn example_s3_event() { + let data = include_bytes!("../../fixtures/example-s3-event.json"); + let parsed: S3Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3Event = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_s3_event_with_decoded() { + let data = include_bytes!("../../fixtures/example-s3-event-with-decoded.json"); + let parsed: S3Event = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3Event = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/s3/mod.rs b/lambda-events/src/event/s3/mod.rs new file mode 100644 index 00000000..b5664585 --- /dev/null +++ b/lambda-events/src/event/s3/mod.rs @@ -0,0 +1,5 @@ +mod event; +pub use self::event::*; + +pub mod batch_job; +pub mod object_lambda; diff --git a/lambda-events/src/event/s3/object_lambda.rs b/lambda-events/src/event/s3/object_lambda.rs new file mode 100644 index 00000000..e31a751e --- /dev/null +++ b/lambda-events/src/event/s3/object_lambda.rs @@ -0,0 +1,171 @@ +use crate::custom_serde::*; +use http::HeaderMap; +use serde::de::DeserializeOwned; +use serde::ser::Serialize; +use serde_json::Value; +use std::collections::HashMap; + +/// `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)] +#[serde(rename_all = "camelCase")] +pub struct S3ObjectLambdaEvent

+where + P: DeserializeOwned, + P: Serialize, +{ + pub x_amz_request_id: String, + pub get_object_context: Option, + pub head_object_context: Option, + pub list_objects_context: Option, + pub list_objects_v2_context: Option, + #[serde(default, bound = "")] + pub configuration: Configuration

, + pub user_request: UserRequest, + pub user_identity: UserIdentity, + pub protocol_version: String, +} + +/// `GetObjectContext` contains the input and output details +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GetObjectContext { + pub input_s3_url: String, + pub output_route: String, + pub output_token: String, +} + +/// `HeadObjectContext` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HeadObjectContext { + pub input_s3_url: String, +} + +/// `ListObjectsContext` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListObjectsContext { + pub input_s3_url: String, +} + +/// `ListObjectsV2Context` +/// for connections to Amazon S3 and S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ListObjectsV2Context { + pub input_s3_url: String, +} + +/// `Configuration` contains information about the Object Lambda access point +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Configuration

+where + P: DeserializeOwned, + P: Serialize, +{ + pub access_point_arn: String, + pub supporting_access_point_arn: String, + #[serde(default, bound = "")] + pub payload: P, +} + +/// `UserRequest` contains information about the original call to S3 Object Lambda +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserRequest { + pub url: String, + #[serde(deserialize_with = "deserialize_headers", default)] + #[serde(serialize_with = "serialize_headers")] + pub headers: HeaderMap, +} + +/// `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, + pub arn: String, + pub account_id: String, + pub access_key_id: String, + pub session_context: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionContext { + pub attributes: HashMap, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionIssuer { + pub r#type: String, + pub principal_id: String, + pub arn: String, + pub account_id: String, + pub user_name: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_get_object_assumed_role() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-assumed-role.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_get_object_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-get-object-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_head_object_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-head-object-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_list_objects_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "s3")] + fn example_object_lambda_event_list_objects_v2_iam() { + let data = include_bytes!("../../fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json"); + let parsed: S3ObjectLambdaEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: S3ObjectLambdaEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/ses/mod.rs b/lambda-events/src/event/ses/mod.rs new file mode 100644 index 00000000..3570652f --- /dev/null +++ b/lambda-events/src/event/ses/mod.rs @@ -0,0 +1,155 @@ +use chrono::{DateTime, Utc}; + +/// `SimpleEmailEvent` is the outer structure of an event sent via SES. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailRecord { + #[serde(default)] + pub event_version: Option, + #[serde(default)] + pub event_source: Option, + pub ses: SimpleEmailService, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailService { + pub mail: SimpleEmailMessage, + pub receipt: SimpleEmailReceipt, + pub content: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailMessage { + pub common_headers: SimpleEmailCommonHeaders, + #[serde(default)] + pub source: Option, + pub timestamp: DateTime, + pub destination: Vec, + pub headers: Vec, + pub headers_truncated: bool, + #[serde(default)] + pub message_id: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailReceipt { + pub recipients: Vec, + pub timestamp: DateTime, + pub spam_verdict: SimpleEmailVerdict, + pub dkim_verdict: SimpleEmailVerdict, + pub dmarc_verdict: SimpleEmailVerdict, + #[serde(default)] + pub dmarc_policy: Option, + pub spf_verdict: SimpleEmailVerdict, + pub virus_verdict: SimpleEmailVerdict, + pub action: SimpleEmailReceiptAction, + pub processing_time_millis: i64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailHeader { + #[serde(default)] + pub name: Option, + #[serde(default)] + pub value: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailCommonHeaders { + pub from: Vec, + pub to: Vec, + #[serde(default)] + pub return_path: Option, + #[serde(default)] + pub message_id: Option, + #[serde(default)] + pub date: Option, + #[serde(default)] + pub subject: Option, +} + +/// `SimpleEmailReceiptAction` is a logical union of fields present in all action +/// 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)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailReceiptAction { + #[serde(default)] + pub type_: Option, + pub topic_arn: Option, + pub bucket_name: Option, + pub object_key: Option, + pub smtp_reply_code: Option, + pub status_code: Option, + pub message: Option, + pub sender: Option, + pub invocation_type: Option, + pub function_arn: Option, + pub organization_arn: Option, +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailVerdict { + #[serde(default)] + pub status: Option, +} + +pub type SimpleEmailDispositionValue = String; + +/// `SimpleEmailDisposition` disposition return for SES to control rule functions +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SimpleEmailDisposition { + pub disposition: SimpleEmailDispositionValue, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "ses")] + fn example_ses_lambda_event() { + let data = include_bytes!("../../fixtures/example-ses-lambda-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "ses")] + fn example_ses_s3_event() { + let data = include_bytes!("../../fixtures/example-ses-s3-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "ses")] + fn example_ses_sns_event() { + let data = include_bytes!("../../fixtures/example-ses-sns-event.json"); + let parsed: SimpleEmailEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SimpleEmailEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/sns/mod.rs b/lambda-events/src/event/sns/mod.rs new file mode 100644 index 00000000..78193fcf --- /dev/null +++ b/lambda-events/src/event/sns/mod.rs @@ -0,0 +1,248 @@ +use crate::custom_serde::*; +use chrono::{DateTime, Utc}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// 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)] +#[serde(rename_all = "PascalCase")] +pub struct SnsEvent { + pub records: Vec, +} + +/// SnsRecord stores information about each record of a SNS event +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +pub struct SnsRecord { + /// A string containing the event source. + pub event_source: String, + + /// A string containing the event version. + pub event_version: String, + + /// A string containing the event subscription ARN. + pub event_subscription_arn: String, + + /// An SNS object representing the SNS message. + pub sns: SnsMessage, +} + +/// SnsMessage stores information about each record of a SNS event +#[derive(Clone, Debug, 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** + #[serde(rename = "Type")] + pub sns_message_type: String, + + /// A Universally Unique Identifier, unique for each message published. For a notification that Amazon SNS resends during a retry, the message ID of the original message is used. + pub message_id: String, + + /// The Amazon Resource Name (ARN) for the topic that this message was published to. + pub topic_arn: String, + + /// The Subject parameter specified when the notification was published to the topic. + /// + /// The SNS Developer Guide states: *This is an optional parameter. If no Subject was specified, then this name-value pair does not appear in this JSON document.* + /// + /// Preliminary tests show this appears in the lambda event JSON as `Subject: null`, marking as Option with need to test additional scenarios + #[serde(default)] + pub subject: Option, + + /// The time (UTC) when the notification was published. + pub timestamp: DateTime, + + /// Version of the Amazon SNS signature used. + pub signature_version: String, + + /// Base64-encoded SHA1withRSA signature of the Message, MessageId, Subject (if present), Type, Timestamp, and TopicArn values. + pub signature: String, + + /// The URL to the certificate that was used to sign the message. + #[serde(alias = "SigningCertURL")] + pub signing_cert_url: String, + + /// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint. + #[serde(alias = "UnsubscribeURL")] + pub unsubscribe_url: String, + + /// The Message value specified when the notification was published to the topic. + pub message: String, + + /// This is a HashMap of defined attributes for a message. Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// 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) +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsEventObj { + pub records: Vec>, +} + +/// Alternative to `SnsRecord`, used alongside `SnsEventObj` and `SnsMessageObj` when deserializing nested objects from within SNS messages) +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsRecordObj { + /// A string containing the event source. + pub event_source: String, + + /// A string containing the event version. + pub event_version: String, + + /// A string containing the event subscription ARN. + pub event_subscription_arn: String, + + /// An SNS object representing the SNS message. + pub sns: SnsMessageObj, +} + +/// 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)] +#[serde(rename_all = "PascalCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SnsMessageObj { + /// The type of SNS message. For a lambda event, this should always be **Notification** + #[serde(rename = "Type")] + pub sns_message_type: String, + + /// A Universally Unique Identifier, unique for each message published. For a notification that Amazon SNS resends during a retry, the message ID of the original message is used. + pub message_id: String, + + /// The Amazon Resource Name (ARN) for the topic that this message was published to. + pub topic_arn: String, + + /// The Subject parameter specified when the notification was published to the topic. + /// + /// The SNS Developer Guide states: *This is an optional parameter. If no Subject was specified, then this name-value pair does not appear in this JSON document.* + /// + /// Preliminary tests show this appears in the lambda event JSON as `Subject: null`, marking as Option with need to test additional scenarios + #[serde(default)] + pub subject: Option, + + /// The time (UTC) when the notification was published. + pub timestamp: DateTime, + + /// Version of the Amazon SNS signature used. + pub signature_version: String, + + /// Base64-encoded SHA1withRSA signature of the Message, MessageId, Subject (if present), Type, Timestamp, and TopicArn values. + pub signature: String, + + /// The URL to the certificate that was used to sign the message. + #[serde(alias = "SigningCertURL")] + pub signing_cert_url: String, + + /// A URL that you can use to unsubscribe the endpoint from this topic. If you visit this URL, Amazon SNS unsubscribes the endpoint and stops sending notifications to this endpoint. + #[serde(alias = "UnsubscribeURL")] + pub unsubscribe_url: String, + + /// Deserialized into a `T` from nested JSON inside the SNS message string. `T` must implement the `Deserialize` or `DeserializeOwned` trait. + #[serde_as(as = "serde_with::json::JsonString")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub message: T, + + /// This is a HashMap of defined attributes for a message. Additional details can be found in the [SNS Developer Guide](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html) + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub message_attributes: HashMap, +} + +/// Structured metadata items (such as timestamps, geospatial data, signatures, and identifiers) about the message. +/// +/// 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) +#[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**. + #[serde(rename = "Type")] + pub data_type: String, + + /// The user-specified message attribute value. + #[serde(rename = "Value")] + pub value: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event() { + let data = include_bytes!("../../fixtures/example-sns-event.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_pascal_case() { + let data = include_bytes!("../../fixtures/example-sns-event-pascal-case.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_cloudwatch_single_metric() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(1, parsed.records.len()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_event_cloudwatch_multiple_metrics() { + let data = include_bytes!("../../fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json"); + let parsed: SnsEvent = serde_json::from_slice(data).unwrap(); + assert_eq!(2, parsed.records.len()); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sns")] + fn my_example_sns_obj_event() { + let data = include_bytes!("../../fixtures/example-sns-event-obj.json"); + + #[derive(Debug, Serialize, Deserialize, Eq, PartialEq)] + struct CustStruct { + foo: String, + bar: i32, + } + + let parsed: SnsEventObj = serde_json::from_slice(data).unwrap(); + println!("{:?}", parsed); + + assert_eq!(parsed.records[0].sns.message.foo, "Hello world!"); + assert_eq!(parsed.records[0].sns.message.bar, 123); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SnsEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/sqs/mod.rs b/lambda-events/src/event/sqs/mod.rs new file mode 100644 index 00000000..5dc178b2 --- /dev/null +++ b/lambda-events/src/event/sqs/mod.rs @@ -0,0 +1,161 @@ +use crate::{custom_serde::*, encodings::Base64Data}; +use serde::de::DeserializeOwned; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// The Event sent to Lambda from SQS. Contains 1 or more individual SQS Messages +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsEvent { + #[serde(rename = "Records")] + pub records: Vec, +} + +/// An individual SQS Message, its metadata, and Message Attributes +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsMessage { + /// 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, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, +} + +/// 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 +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +pub struct SqsEventObj { + #[serde(rename = "Records")] + #[serde(bound(deserialize = "T: DeserializeOwned"))] + pub records: Vec>, +} + +/// 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 +#[serde_with::serde_as] +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(bound(deserialize = "T: DeserializeOwned"))] +#[serde(rename_all = "camelCase")] +pub struct SqsMessageObj { + /// 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, + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + #[serde(default)] + pub event_source: Option, + #[serde(default)] + pub aws_region: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsMessageAttribute { + pub string_value: Option, + pub binary_value: Option, + #[serde(default)] + pub string_list_values: Vec, + #[serde(default)] + pub binary_list_values: Vec, + #[serde(default)] + pub data_type: Option, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsBatchResponse { + pub batch_item_failures: Vec, +} + +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BatchItemFailure { + pub item_identifier: String, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_event() { + let data = include_bytes!("../../fixtures/example-sqs-event.json"); + let parsed: SqsEvent = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsEvent = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_obj_event() { + #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] + struct CustStruct { + a: String, + b: u32, + } + + let data = include_bytes!("../../fixtures/example-sqs-event-obj.json"); + let parsed: SqsEventObj = serde_json::from_slice(data).unwrap(); + + assert_eq!(parsed.records[0].body.a, "Test"); + assert_eq!(parsed.records[0].body.b, 123); + + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsEventObj = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } + + #[test] + #[cfg(feature = "sqs")] + fn example_sqs_batch_response() { + // Example sqs batch response fetched 2022-05-13, from: + // https://docs.aws.amazon.com/lambda/latest/dg/with-sqs.html#services-sqs-batchfailurereporting + let data = include_bytes!("../../fixtures/example-sqs-batch-response.json"); + let parsed: SqsBatchResponse = serde_json::from_slice(data).unwrap(); + let output: String = serde_json::to_string(&parsed).unwrap(); + let reparsed: SqsBatchResponse = serde_json::from_slice(output.as_bytes()).unwrap(); + assert_eq!(parsed, reparsed); + } +} diff --git a/lambda-events/src/event/streams/mod.rs b/lambda-events/src/event/streams/mod.rs new file mode 100644 index 00000000..51a77121 --- /dev/null +++ b/lambda-events/src/event/streams/mod.rs @@ -0,0 +1,44 @@ +/// `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, +} + +/// `KinesisBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct KinesisBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} + +/// `DynamoDbEventResponse` is the outer structure to report batch item failures for DynamoDBEvent. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DynamoDbEventResponse { + pub batch_item_failures: Vec, +} + +/// `DynamoDbBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct DynamoDbBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} + +/// `SqsEventResponse` is the outer structure to report batch item failures for SQSEvent. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsEventResponse { + pub batch_item_failures: Vec, +} + +/// `SqsBatchItemFailure` is the individual record which failed processing. +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SqsBatchItemFailure { + #[serde(default)] + pub item_identifier: Option, +} diff --git a/lambda-events/src/fixtures/example-activemq-event.json b/lambda-events/src/fixtures/example-activemq-event.json new file mode 100644 index 00000000..48ae90cd --- /dev/null +++ b/lambda-events/src/fixtures/example-activemq-event.json @@ -0,0 +1,25 @@ +{ + "eventSource": "aws:mq", + "eventSourceArn": "arn:aws:mq:us-west-2:533019413397:broker:shask-test:b-0f5b7522-2b41-4f85-a615-735a4e6d96b5", + "messages": [ + { + "messageID": "ID:b-0f5b7522-2b41-4f85-a615-735a4e6d96b5-2.mq.us-west-2.amazonaws.com-34859-1598944546501-4:12:1:1:3", + "messageType": "jms/text-message", + "timestamp": 1599863938941, + "deliveryMode": 1, + "correlationID": "", + "replyTo": "null", + "destination": { + "physicalName": "testQueue" + }, + "redelivered": false, + "type": "", + "expiration": 0, + "priority": 0, + "data": "RW50ZXIgc29tZSB0ZXh0IGhlcmUgZm9yIHRoZSBtZXNzYWdlIGJvZHkuLi4=", + "brokerInTime": 1599863938943, + "brokerOutTime": 1599863938944, + "properties": {"testKey": "testValue"} + } + ] +} diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json b/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json new file mode 100644 index 00000000..8fc2ec51 --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-request-headers-only.json @@ -0,0 +1,26 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefg" + } + }, + "httpMethod": "GET", + "path": "/", + "queryStringParameters": { + "key": "hello" + }, + "headers": { + "accept": "*/*", + "connection": "keep-alive", + "host": "lambda-test-alb-1334523864.us-east-1.elb.amazonaws.com", + "user-agent": "curl/7.54.0", + "x-amzn-trace-id": "Root=1-5c34e93e-4dea0086f9763ac0667b115a", + "x-forwarded-for": "25.12.198.67", + "x-forwarded-port": "80", + "x-forwarded-proto": "http", + "x-imforwards": "20", + "x-myheader": "123" + }, + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json b/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json new file mode 100644 index 00000000..7d54621e --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-request-multivalue-headers.json @@ -0,0 +1,49 @@ +{ + "requestContext": { + "elb": { + "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/lambda-target/abcdefgh" + } + }, + "httpMethod": "GET", + "path": "/", + "multiValueQueryStringParameters": { + "key": [ + "hello" + ] + }, + "multiValueHeaders": { + "accept": [ + "*/*" + ], + "connection": [ + "keep-alive" + ], + "host": [ + "lambda-test-alb-1234567.us-east-1.elb.amazonaws.com" + ], + "user-agent": [ + "curl/7.54.0" + ], + "x-amzn-trace-id": [ + "Root=1-5c34e7d4-00ca239424b68028d4c56d68" + ], + "x-forwarded-for": [ + "72.21.198.67" + ], + "x-forwarded-port": [ + "80" + ], + "x-forwarded-proto": [ + "http" + ], + "x-imforwards": [ + "20" + ], + "x-myheader": [ + "123" + ] + }, + "body": "Some text", + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-alb-lambda-target-response.json b/lambda-events/src/fixtures/example-alb-lambda-target-response.json new file mode 100644 index 00000000..1411bf34 --- /dev/null +++ b/lambda-events/src/fixtures/example-alb-lambda-target-response.json @@ -0,0 +1,15 @@ +{ + "isBase64Encoded": false, + "statusCode": 200, + "statusDescription": "200 OK", + "headers": { + "Set-cookie": "cookies", + "Content-Type": "application/json" + }, + "multiValueHeaders": { + "Set-cookie": ["cookie-name=cookie-value;Domain=myweb.com;Secure;HttpOnly","cookie-name=cookie-value;Expires=May 8, 2019"], + "Content-Type": ["application/json"] + }, + "body": "Hello from Lambda" +} + diff --git a/lambda-events/src/fixtures/example-apigw-console-test-request.json b/lambda-events/src/fixtures/example-apigw-console-test-request.json new file mode 100644 index 00000000..ba119d85 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-console-test-request.json @@ -0,0 +1,45 @@ +{ + "body": "{\r\n\t\"a\": 1\r\n}", + "headers":null, + "httpMethod":"POST", + "isBase64Encoded":false, + "multiValueHeaders":null, + "multiValueQueryStringParameters":null, + "path":"/myPath", + "pathParameters":null, + "queryStringParameters":null, + "requestContext":{ + "accountId":"xxxxx", + "apiId":"xxxxx", + "domainName":"testPrefix.testDomainName", + "domainPrefix":"testPrefix", + "extendedRequestId":"NvWWKEZbliAFliA=", + "httpMethod":"POST", + "identity":{ + "accessKey":"xxxxx", + "accountId":"xxxxx", + "apiKey":"test-invoke-api-key", + "apiKeyId":"test-invoke-api-key-id", + "caller":"xxxxx:xxxxx", + "cognitoAuthenticationProvider":null, + "cognitoAuthenticationType":null, + "cognitoIdentityId":null, + "cognitoIdentityPoolId":null, + "principalOrgId":null, + "sourceIp":"test-invoke-source-ip", + "user":"xxxxx:xxxxx", + "userAgent":"aws-internal/3 aws-sdk-java/1.12.154 Linux/5.4.156-94.273.amzn2int.x86_64 OpenJDK_64-Bit_Server_VM/25.322-b06 java/1.8.0_322 vendor/Oracle_Corporation cfg/retry-mode/standard", + "userArn":"arn:aws:sts::xxxxx:assumed-role/xxxxx/xxxxx" + }, + "path":"/myPath", + "protocol":"HTTP/1.1", + "requestId":"e5488776-afe4-4e5e-92b1-37bd23f234d6", + "requestTime":"18/Feb/2022:13:23:12 +0000", + "requestTimeEpoch":1645190592806, + "resourceId":"ddw8yd", + "resourcePath":"/myPath", + "stage":"test-invoke-stage" + }, + "resource":"/myPath", + "stageVariables":null +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json b/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json new file mode 100644 index 00000000..101c63a7 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-request-type-request.json @@ -0,0 +1,88 @@ +{ + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:us-east-1:123456789012:s4x3opwd6i/test/GET/request", + "resource": "/request", + "path": "/request", + "httpMethod": "GET", + "headers": { + "X-AMZ-Date": "20170718T062915Z", + "Accept": "*/*", + "HeaderAuth1": "headerValue1", + "CloudFront-Viewer-Country": "US", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "User-Agent": "...", + "X-Forwarded-Proto": "https", + "CloudFront-Is-SmartTV-Viewer": "false", + "Host": "....execute-api.us-east-1.amazonaws.com", + "Accept-Encoding": "gzip, deflate", + "X-Forwarded-Port": "443", + "X-Amzn-Trace-Id": "...", + "Via": "...cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "...", + "X-Forwarded-For": "..., ...", + "Postman-Token": "...", + "cache-control": "no-cache", + "CloudFront-Is-Desktop-Viewer": "true", + "Content-Type": "application/x-www-form-urlencoded" + }, + "multiValueHeaders": { + "X-AMZ-Date": ["20170718T062915Z"], + "Accept": ["*/*"], + "HeaderAuth1": ["headerValue1"], + "CloudFront-Viewer-Country": ["US"], + "CloudFront-Forwarded-Proto": ["https"], + "CloudFront-Is-Tablet-Viewer": ["false"], + "CloudFront-Is-Mobile-Viewer": ["false"], + "User-Agent": ["..."], + "X-Forwarded-Proto": ["https"], + "CloudFront-Is-SmartTV-Viewer": ["false"], + "Host": ["....execute-api.us-east-1.amazonaws.com"], + "Accept-Encoding": ["gzip, deflate"], + "X-Forwarded-Port": ["443"], + "X-Amzn-Trace-Id": ["..."], + "Via": ["...cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["..."], + "X-Forwarded-For": ["..., ..."], + "Postman-Token": ["..."], + "cache-control": ["no-cache"], + "CloudFront-Is-Desktop-Viewer": ["true"], + "Content-Type": ["application/x-www-form-urlencoded"] + }, + "queryStringParameters": { + "QueryString1": "queryValue1" + }, + "multiValueQueryStringParameters": { + "QueryString1": ["queryValue1"] + }, + "pathParameters": {}, + "stageVariables": { + "StageVar1": "stageValue1" + }, + "requestContext": { + "path": "/request", + "accountId": "123456789012", + "resourceId": "05c7jb", + "stage": "test", + "requestId": "...", + "identity": { + "apiKey": "...", + "sourceIp": "...", + "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" + } + } + }, + "resourcePath": "/request", + "httpMethod": "GET", + "apiId": "s4x3opwd6i" + } +} + diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-request.json b/lambda-events/src/fixtures/example-apigw-custom-auth-request.json new file mode 100644 index 00000000..9ff8134e --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-request.json @@ -0,0 +1,5 @@ +{ + "type":"TOKEN", + "authorizationToken":"allow", + "methodArn":"arn:aws:execute-api:us-west-2:123456789012:ymy8tbxw7b/*/GET/" +} diff --git a/lambda-events/src/fixtures/example-apigw-custom-auth-response.json b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json new file mode 100644 index 00000000..9b624141 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-custom-auth-response.json @@ -0,0 +1,19 @@ +{ + "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}" +} diff --git a/lambda-events/src/fixtures/example-apigw-request.json b/lambda-events/src/fixtures/example-apigw-request.json new file mode 100644 index 00000000..570f785b --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-request.json @@ -0,0 +1,96 @@ +{ + "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" + }, + "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}" +} + diff --git a/lambda-events/src/fixtures/example-apigw-response.json b/lambda-events/src/fixtures/example-apigw-response.json new file mode 100644 index 00000000..ba87222e --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-response.json @@ -0,0 +1,42 @@ +{ + "statusCode": 200, + "headers": { + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, lzma, sdch, br", + "Accept-Language": "en-US,en;q=0.8", + "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": "wt6mne2s9k.execute-api.us-west-2.amazonaws.com", + "Upgrade-Insecure-Requests": "1", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48", + "Via": "1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)", + "X-Amz-Cf-Id": "nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g==", + "X-Forwarded-For": "192.168.100.1, 192.168.1.1", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"], + "Accept-Encoding": ["gzip, deflate, lzma, sdch, br"], + "Accept-Language": ["en-US,en;q=0.8"], + "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": ["wt6mne2s9k.execute-api.us-west-2.amazonaws.com"], + "Upgrade-Insecure-Requests": ["1"], + "User-Agent": ["Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.82 Safari/537.36 OPR/39.0.2256.48"], + "Via": ["1.1 fb7cca60f0ecd82ce07790c9c5eef16c.cloudfront.net (CloudFront)"], + "X-Amz-Cf-Id": ["nBsWBOrSHMgnaROZJK1wGCZ9PcRcSpq_oSXZNQwQ10OTZL4cimZo3g=="], + "X-Forwarded-For": ["192.168.100.1, 192.168.1.1"], + "X-Forwarded-Port": ["443"], + "X-Forwarded-Proto": ["https"] + }, + "body": "Hello World" + } diff --git a/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json b/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json new file mode 100644 index 00000000..906d0eb5 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-restapi-openapi-request.json @@ -0,0 +1,97 @@ +{ + "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", + "operationName": "HelloWorld", + "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}" +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json new file mode 100644 index 00000000..c220f37b --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v1-request.json @@ -0,0 +1,61 @@ +{ + "version": "1.0", + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": "user1,123", + "authorizationToken": "user1,123", + "resource": "/request", + "path": "/request", + "httpMethod": "GET", + "headers": { + "X-AMZ-Date": "20170718T062915Z", + "Accept": "*/*", + "HeaderAuth1": "headerValue1", + "CloudFront-Viewer-Country": "US", + "CloudFront-Forwarded-Proto": "https", + "CloudFront-Is-Tablet-Viewer": "false", + "CloudFront-Is-Mobile-Viewer": "false", + "User-Agent": "..." + }, + "queryStringParameters": { + "QueryString1": "queryValue1" + }, + "pathParameters": {}, + "stageVariables": { + "StageVar1": "stageValue1" + }, + "requestContext": { + "path": "/request", + "accountId": "123456789012", + "resourceId": "05c7jb", + "stage": "test", + "requestId": "...", + "identity": { + "apiKey": "...", + "sourceIp": "...", + "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" + } + } + }, + "http": { + "method": "POST", + "path": "/my/path", + "protocol": "HTTP/1.1", + "sourceIp": "IP", + "userAgent": "agent" + }, + "resourcePath": "/request", + "httpMethod": "GET", + "apiId": "abcdef123", + "routeKey": "$default", + "time": "12/Mar/2020:19:03:58 +0000", + "timeEpoch": 1583348638390 + } +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json new file mode 100644 index 00000000..a70cac91 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request-without-cookies.json @@ -0,0 +1,49 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": ["user1", "123"], + "routeKey": "$default", + "rawPath": "/my/path", + "rawQueryString": "parameter1=value1¶meter1=value2¶meter2=value", + "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" } +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json new file mode 100644 index 00000000..59166c8c --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-v2-request.json @@ -0,0 +1,51 @@ +{ + "version": "2.0", + "type": "REQUEST", + "routeArn": "arn:aws:execute-api:us-east-1:123456789012:abcdef123/test/GET/request", + "identitySource": ["user1", "123"], + "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" } +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json new file mode 100644 index 00000000..1e741abf --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-custom-authorizer-websocket-request.json @@ -0,0 +1,51 @@ +{ + "version": "$LATEST", + "type": "REQUEST", + "methodArn": "arn:aws:execute-api:eu-west-1:123456789012:abcdef123/test/$connect", + "headers": { + "Header1": "value1", + "Header2": "value2" + }, + "multiValueHeaders": { + "Header1": ["value1"], + "Header2": ["value2"] + }, + "multiValueQueryStringParameters": {}, + "queryStringParameters": { + "parameter1": "value1,value2", + "parameter2": "value" + }, + "requestContext": { + "apiId": "api-id", + "connectedAt": 1655103417248, + "connectionId": "connection-id", + "domainName": "example.com", + "eventType": "CONNECT", + "extendedRequestId": "extended-req-id", + "identity": { + "sourceIp": "1.2.3.4", + "userAgent": "user-agent" + }, + "messageDirection": "IN", + "requestId": "req-id", + "requestTime": "13/Jun/2022:06:56:57 +0000", + "requestTimeEpoch": 1655103417249, + "routeKey": "$connect", + "stage": "test" + }, + "stageVariables": {}, + "context": { + "request_id": "5req-id" + }, + "deadline": 1655103420483, + "invoked_function_arn": "arn:aws:lambda:eu-west-1:123456789012:function:a-lambda", + "xray_trace_id": "xray-trace-id", + "client_context": null, + "identity": null, + "env_config": { + "function_name": "a-lambda" + }, + "memory": 128, + "log_stream": "log-stream-id", + "log_group": "log-group-id" +} diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-iam.json b/lambda-events/src/fixtures/example-apigw-v2-request-iam.json new file mode 100644 index 00000000..b78edb3d --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-iam.json @@ -0,0 +1,73 @@ +{ + "version": "2.0", + "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" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "iam": { + "accessKey": "ARIA2ZJZYVUEREEIHAKY", + "accountId": "1234567890", + "callerId": "AROA7ZJZYVRE7C3DUXHH6:CognitoIdentityCredentials", + "cognitoIdentity": { + "amr" : ["foo"], + "identityId": "us-east-1:3f291106-8703-466b-8f2b-3ecee1ca56ce", + "identityPoolId": "us-east-1:4f291106-8703-466b-8f2b-3ecee1ca56ce" + }, + "principalOrgId": "AwsOrgId", + "userArn": "arn:aws:iam::1234567890:user/Admin", + "userId": "AROA2ZJZYVRE7Y3TUXHH6" + } + }, + "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 +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json new file mode 100644 index 00000000..e3422a9a --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-jwt-authorizer.json @@ -0,0 +1,70 @@ +{ + "version": "2.0", + "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" + }, + "pathParameters": { + "proxy": "hello/world" + }, + "requestContext": { + "routeKey": "$default", + "accountId": "123456789012", + "stage": "$default", + "requestId": "id", + "authorizer": { + "jwt": { + "claims": { + "claim1": "value1", + "claim2": "value2" + }, + "scopes": [ + "scope1", + "scope2" + ] + } + }, + "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 +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json new file mode 100644 index 00000000..60263e99 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-lambda-authorizer.json @@ -0,0 +1,63 @@ +{ + "version": "2.0", + "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" + }, + "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 +} + diff --git a/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json b/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json new file mode 100644 index 00000000..9f7a7838 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-v2-request-no-authorizer.json @@ -0,0 +1,48 @@ +{ + "version": "2.0", + "routeKey": "$default", + "rawPath": "/", + "rawQueryString": "", + "headers": { + "accept": "*/*", + "content-length": "0", + "host": "aaaaaaaaaa.execute-api.us-west-2.amazonaws.com", + "user-agent": "curl/7.58.0", + "x-amzn-trace-id": "Root=1-5e9f0c65-1de4d666d4dd26aced652b6c", + "x-forwarded-for": "1.2.3.4", + "x-forwarded-port": "443", + "x-forwarded-proto": "https" + }, + "requestContext": { + "accountId": "123456789012", + "apiId": "aaaaaaaaaa", + "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": "aaaaaaaaaa.execute-api.us-west-2.amazonaws.com", + "domainPrefix": "aaaaaaaaaa", + "http": { + "method": "GET", + "path": "/", + "protocol": "HTTP/1.1", + "sourceIp": "1.2.3.4", + "userAgent": "curl/7.58.0" + }, + "requestId": "LV7fzho-PHcEJPw=", + "routeKey": "$default", + "stage": "$default", + "time": "21/Apr/2020:15:08:21 +0000", + "timeEpoch": 1587481701067 + }, + "isBase64Encoded": false +} + diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json b/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json new file mode 100644 index 00000000..27ec3479 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request-without-method.json @@ -0,0 +1,58 @@ +{ + "headers": { + "Host": "asdfasdf.execute-api.us-west-2.amazonaws.com", + "Sec-WebSocket-Key": "asdfasdf==", + "Sec-WebSocket-Version": "13", + "X-Amzn-Trace-Id": "Root=1-asdf-asdfasdf", + "x-api-key": "asdfasdf", + "X-Forwarded-For": "10.0.0.13", + "X-Forwarded-Port": "443", + "X-Forwarded-Proto": "https" + }, + "multiValueHeaders": { + "Host": [ + "asdfasdf.execute-api.us-west-2.amazonaws.com" + ], + "Sec-WebSocket-Key": [ + "asdfasdf==" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Amzn-Trace-Id": [ + "Root=1-asdf-asdfasdf" + ], + "x-api-key": [ + "asdfasdf" + ], + "X-Forwarded-For": [ + "10.0.0.13" + ], + "X-Forwarded-Port": [ + "443" + ], + "X-Forwarded-Proto": [ + "https" + ] + }, + "requestContext": { + "routeKey": "$connect", + "eventType": "CONNECT", + "extendedRequestId": "asdfasdf=", + "requestTime": "22/Feb/2022:19:07:37 +0000", + "messageDirection": "IN", + "stage": "dev", + "connectedAt": 1645556857902, + "requestTimeEpoch": 1645556857902, + "identity": { + "apiKey": "asdfasdf", + "apiKeyId": "asdf", + "sourceIp": "10.0.0.13" + }, + "requestId": "asdf=", + "domainName": "asdfasdf.execute-api.us-west-2.amazonaws.com", + "connectionId": "asdfasdf=", + "apiId": "asdfasdf" + }, + "isBase64Encoded": false +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-apigw-websocket-request.json b/lambda-events/src/fixtures/example-apigw-websocket-request.json new file mode 100644 index 00000000..2628ae26 --- /dev/null +++ b/lambda-events/src/fixtures/example-apigw-websocket-request.json @@ -0,0 +1,108 @@ +{ + "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": { + "Host": [ + "*.execute-api.eu-central-1.amazonaws.com" + ], + "Sec-WebSocket-Extensions": [ + "permessage-deflate; client_max_window_bits" + ], + "Sec-WebSocket-Key": [ + "*" + ], + "Sec-WebSocket-Version": [ + "13" + ], + "X-Amzn-Trace-Id": [ + "Root=*" + ], + "X-Forwarded-For": [ + "*.*.*.*" + ], + "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", + "stage": "testStage", + "requestId": "deef4878-7910-11e6-8f14-25afc3e9ae33", + "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" + }, + "resourcePath": "/{proxy+}", + "authorizer": { + "principalId": "admin", + "clientId": 1, + "clientName": "Exata" + }, + "httpMethod": "POST", + "apiId": "gy415nuibc", + "connectedAt": 1547230720092, + "connectionId": "TWegAcC4EowCHnA=", + "domainName": "*.execute-api.eu-central-1.amazonaws.com", + "error": "*", + "eventType": "CONNECT", + "extendedRequestId": "TWegAcC4EowCHnA=", + "integrationLatency": "123", + "messageDirection": "IN", + "messageId": null, + "requestTime": "07/Jan/2019:09:20:57 +0000", + "requestTimeEpoch": 0, + "routeKey": "$connect", + "status": "*" + }, + "body": "{\r\n\t\"a\": 1\r\n}" +} + diff --git a/lambda-events/src/fixtures/example-appsync-batchinvoke.json b/lambda-events/src/fixtures/example-appsync-batchinvoke.json new file mode 100644 index 00000000..cd6a323a --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-batchinvoke.json @@ -0,0 +1,28 @@ +{ + "version": "2017-02-28", + "operation": "BatchInvoke", + "payload": [{ + "arguments": { + "id": "postId1", + "count": 1, + "float": 1.2, + "flag": true + } + }, + { + "arguments": { + "id": "postId2", + "count": 2, + "float": 1.2 + } + }, + { + "arguments": { + "id": "postId3", + "count": 3, + "flag": false + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-appsync-identity-cognito.json b/lambda-events/src/fixtures/example-appsync-identity-cognito.json new file mode 100644 index 00000000..266774b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-identity-cognito.json @@ -0,0 +1,19 @@ +{ + "sub": "123-456", + "issuer": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc", + "username": "user1", + "claims": { + "sub": "123-456", + "aud": "abcdefg", + "event_id": "123-123-123", + "token_use": "id", + "auth_time": 1551226125, + "iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_abc", + "cognito:username": "user1", + "exp": 1551228178628, + "iat": 1551228178629 + }, + "sourceIp": ["192.168.196.186", "193.168.196.186"], + "defaultAuthStrategy": "ALLOW" +} + diff --git a/lambda-events/src/fixtures/example-appsync-identity-iam.json b/lambda-events/src/fixtures/example-appsync-identity-iam.json new file mode 100644 index 00000000..ffeaa707 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-identity-iam.json @@ -0,0 +1,11 @@ +{ + "accountId": "accountid123", + "cognitoIdentityPoolId": "identitypoolid123", + "cognitoIdentityId": "identityid123", + "cognitoIdentityAuthType": "authenticated", + "cognitoIdentityAuthProvider": "providerABC", + "sourceIp": ["192.168.196.186", "193.168.196.186"], + "username": "user1", + "userArn": "arn:aws:iam::123456789012:user/appsync" +} + diff --git a/lambda-events/src/fixtures/example-appsync-invoke.json b/lambda-events/src/fixtures/example-appsync-invoke.json new file mode 100644 index 00000000..7943e082 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-invoke.json @@ -0,0 +1,14 @@ +{ + "version": "2017-02-28", + "operation": "Invoke", + "payload": { + "field": "getPost", + "arguments": { + "id": "postId1", + "count": 1, + "float": 1.2, + "flag": true + } + } +} + diff --git a/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json b/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json new file mode 100644 index 00000000..bcc28672 --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-lambda-auth-request.json @@ -0,0 +1,12 @@ +{ + "authorizationToken": "ExampleAUTHtoken123123123", + "requestContext": { + "apiId": "aaaaaa123123123example123", + "accountId": "111122223333", + "requestId": "f4081827-1111-4444-5555-5cf4695f339f", + "queryString": "mutation CreateEvent {...}\n\nquery MyQuery {...}\n", + "operationName": "MyQuery", + "variables": {} + } +} + diff --git a/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json b/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json new file mode 100644 index 00000000..461c4a4d --- /dev/null +++ b/lambda-events/src/fixtures/example-appsync-lambda-auth-response.json @@ -0,0 +1,8 @@ +{ + "isAuthorized": true, + "resolverContext": { + "banana": "very yellow", + "apple": "very green" + } +} + diff --git a/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json b/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json new file mode 100644 index 00000000..d144c10b --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-launch-successful.json @@ -0,0 +1,29 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Launch Successful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "InProgress", + "Description": "Launching a new EC2 instance: i-12345678", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json b/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json new file mode 100644 index 00000000..e446d90d --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-launch-unsuccessful.json @@ -0,0 +1,28 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Launch Unsuccessful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "Failed", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "message-text", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json b/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json new file mode 100644 index 00000000..cd19446a --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-lifecycle-action.json @@ -0,0 +1,21 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance-launch Lifecycle Action", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn" + ], + "detail": { + "LifecycleActionToken": "87654321-4321-4321-4321-210987654321", + "AutoScalingGroupName": "my-asg", + "LifecycleHookName": "my-lifecycle-hook", + "EC2InstanceId": "i-1234567890abcdef0", + "LifecycleTransition": "autoscaling:EC2_INSTANCE_LAUNCHING", + "NotificationMetadata": "additional-info" + } + } + diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json new file mode 100644 index 00000000..7afd13c7 --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-action.json @@ -0,0 +1,19 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance-terminate Lifecycle Action", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn" + ], + "detail": { + "LifecycleActionToken":"87654321-4321-4321-4321-210987654321", + "AutoScalingGroupName":"my-asg", + "LifecycleHookName":"my-lifecycle-hook", + "EC2InstanceId":"i-1234567890abcdef0", + "LifecycleTransition":"autoscaling:EC2_INSTANCE_TERMINATING" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json new file mode 100644 index 00000000..cf6d1b0a --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-successful.json @@ -0,0 +1,29 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Terminate Successful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "InProgress", + "Description": "Terminating EC2 instance: i-12345678", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json b/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json new file mode 100644 index 00000000..ff973d97 --- /dev/null +++ b/lambda-events/src/fixtures/example-autoscaling-event-terminate-unsuccessful.json @@ -0,0 +1,28 @@ +{ + "version": "0", + "id": "12345678-1234-1234-1234-123456789012", + "detail-type": "EC2 Instance Terminate Unsuccessful", + "source": "aws.autoscaling", + "account": "123456789012", + "time": "1970-01-01T00:00:00Z", + "region": "us-west-2", + "resources": [ + "auto-scaling-group-arn", + "instance-arn" + ], + "detail": { + "StatusCode": "Failed", + "AutoScalingGroupName": "my-auto-scaling-group", + "ActivityId": "87654321-4321-4321-4321-210987654321", + "Details": { + "Availability Zone": "us-west-2b", + "Subnet ID": "subnet-12345678" + }, + "RequestId": "12345678-1234-1234-1234-123456789012", + "StatusMessage": "message-text", + "EndTime": "1970-01-01T00:00:00Z", + "EC2InstanceId": "i-1234567890abcdef0", + "StartTime": "1970-01-01T00:00:00Z", + "Cause": "description-text" + } + } diff --git a/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json b/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json new file mode 100644 index 00000000..92d59ee3 --- /dev/null +++ b/lambda-events/src/fixtures/example-clientvpn-connectionhandler-request.json @@ -0,0 +1,12 @@ +{ + "connection-id": "cvpn-connection-04e7e1b2f0daf9460", + "endpoint-id": "cvpn-endpoint-0f13eab7f860433cc", + "common-name": "", + "username": "username", + "platform": "", + "platform-version": "", + "public-ip": "10.11.12.13", + "client-openvpn-version": "", + "schema-version": "v1" +} + diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json new file mode 100644 index 00000000..94a10f96 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-multiple-metrics.json @@ -0,0 +1,40 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\":\"EXAMPLE\",\"AlarmDescription\":\"EXAMPLE\",\"AWSAccountId\":\"EXAMPLE\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2015-06-03T17:43:27.123+0000\",\"Region\":\"EXAMPLE\",\"AlarmArn\":\"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\":\"INSUFFICIENT_DATA\",\"Trigger\":{\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":0.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\",\"Metrics\":[{\"Expression\":\"m1*1\",\"Id\":\"e1\",\"Label\":\"Expression1\",\"ReturnData\":true},{\"Id\":\"m1\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"TestInstance\",\"name\":\"InstanceId\"}],\"MetricName\":\"NetworkOut\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false}]}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + }, + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\":\"EXAMPLE\",\"AlarmDescription\":\"EXAMPLE\",\"AWSAccountId\":\"EXAMPLE\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"2015-06-03T17:43:27.123+0000\",\"Region\":\"EXAMPLE\",\"AlarmArn\":\"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\":\"INSUFFICIENT_DATA\",\"Trigger\":{\"Period\":60,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":0.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\",\"Metrics\":[{\"Expression\":\"m1*1\",\"Id\":\"e1\",\"Label\":\"Expression1\",\"ReturnData\":true},{\"Id\":\"m1\",\"MetricStat\":{\"Metric\":{\"Dimensions\":[{\"value\":\"TestInstance\",\"name\":\"InstanceId\"}],\"MetricName\":\"NetworkOut\",\"Namespace\":\"AWS/EC2\"},\"Period\":60,\"Stat\":\"Average\"},\"ReturnData\":false}]}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + } + ] +} diff --git a/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json new file mode 100644 index 00000000..8f58b797 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch-alarm-sns-payload-single-metric.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventSource": "aws:sns", + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "Sns": { + "Type": "Notification", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke", + "Message": "{\"AlarmName\": \"EXAMPLE\",\"AlarmDescription\": \"EXAMPLE\",\"AWSAccountId\": \"123456789012\",\"NewStateValue\": \"ALARM\",\"NewStateReason\": \"Threshold Crossed: 1 out of the last 1 datapoints [1234.0 (06/03/15 17:43:27)] was greater than the threshold (0.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\": \"2015-06-03T17:43:27.123+0000\",\"Region\": \"EXAMPLE\",\"AlarmArn\": \"arn:aws:cloudwatch:REGION:ACCOUNT_NUMBER:alarm:EXAMPLE\",\"OldStateValue\": \"INSUFFICIENT_DATA\",\"Trigger\": {\"MetricName\": \"NetworkOut\",\"Namespace\": \"AWS/EC2\",\"StatisticType\": \"Statistic\",\"Statistic\": \"AVERAGE\",\"Unit\": \"Bytes\",\"Dimensions\": [{\"value\": \"TestInstance\",\"name\": \"InstanceId\"}],\"Period\": 60,\"EvaluationPeriods\": 1,\"ComparisonOperator\": \"GreaterThanThreshold\",\"Threshold\": 0.0,\"TreatMissingData\": \"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\": \"\"}}", + "Timestamp": "2015-06-03T17:43:27.123Z", + "SignatureVersion": "1", + "Signature": "EXAMPLE", + "SigningCertURL": "EXAMPLE", + "UnsubscribeURL": "EXAMPLE", + "MessageAttributes": {} + } + } + ] +} diff --git a/lambda-events/src/fixtures/example-cloudwatch_logs-event.json b/lambda-events/src/fixtures/example-cloudwatch_logs-event.json new file mode 100644 index 00000000..e6aae4d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cloudwatch_logs-event.json @@ -0,0 +1,6 @@ +{ + "awslogs": { + "data": "H4sIAAAAAAAAAHWPwQqCQBCGX0Xm7EFtK+smZBEUgXoLCdMhFtKV3akI8d0bLYmibvPPN3wz00CJxmQnTO41whwWQRIctmEcB6sQbFC3CjW3XW8kxpOpP+OC22d1Wml1qZkQGtoMsScxaczKN3plG8zlaHIta5KqWsozoTYw3/djzwhpLwivWFGHGpAFe7DL68JlBUk+l7KSN7tCOEJ4M3/qOI49vMHj+zCKdlFqLaU2ZHV2a4Ct/an0/ivdX8oYc1UVX860fQDQiMdxRQEAAA==" + } +} + diff --git a/lambda-events/src/fixtures/example-code_commit-event.json b/lambda-events/src/fixtures/example-code_commit-event.json new file mode 100644 index 00000000..751e1afa --- /dev/null +++ b/lambda-events/src/fixtures/example-code_commit-event.json @@ -0,0 +1,27 @@ +{ + "Records": [ + { + "eventId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventVersion": "1.0", + "eventTime": "2018-01-26T15:58:33.475+0000", + "eventTriggerName": "my-trigger", + "eventPartNumber": 1, + "codecommit": { + "references": [ + { + "commit": "5c4ef1049f1d27deadbeeff313e0730018be182b", + "ref": "refs/heads/master" + } + ] + }, + "eventName": "TriggerEventTest", + "eventTriggerConfigId": "5a824061-17ca-46a9-bbf9-114edeadbeef", + "eventSourceARN": "arn:aws:codecommit:us-east-1:123456789012:my-repo", + "userIdentityARN": "arn:aws:iam::123456789012:root", + "eventSource": "aws:codecommit", + "awsRegion": "us-east-1", + "eventTotalParts": 1 + } + ] +} + diff --git a/lambda-events/src/fixtures/example-codebuild-phase-change.json b/lambda-events/src/fixtures/example-codebuild-phase-change.json new file mode 100644 index 00000000..5f2f5f5d --- /dev/null +++ b/lambda-events/src/fixtures/example-codebuild-phase-change.json @@ -0,0 +1,138 @@ +{ + "version": "0", + "id": "43ddc2bd-af76-9ca5-2dc7-b695e15adeEX", + "detail-type": "CodeBuild Build Phase Change", + "source": "aws.codebuild", + "account": "123456789012", + "time": "2017-09-01T16:14:21Z", + "region": "us-west-2", + "resources":[ + "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX" + ], + "detail":{ + "completed-phase": "COMPLETED", + "project-name": "my-sample-project", + "build-id": "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX", + "completed-phase-context": "[]", + "additional-information": { + "artifact": { + "md5sum": "da9c44c8a9a3cd4b443126e823168fEX", + "sha256sum": "6ccc2ae1df9d155ba83c597051611c42d60e09c6329dcb14a312cecc0a8e39EX", + "location": "arn:aws:s3:::codebuild-123456789012-output-bucket/my-output-artifact.zip" + }, + "environment": { + "image": "aws/codebuild/standard:2.0", + "privileged-mode": false, + "compute-type": "BUILD_GENERAL1_SMALL", + "type": "LINUX_CONTAINER", + "environment-variables": [] + }, + "timeout-in-minutes": 60.0, + "build-complete": true, + "build-number": 55.0, + "initiator": "MyCodeBuildDemoUser", + "build-start-time": "Sep 1, 2017 4:12:29 PM", + "source": { + "location": "codebuild-123456789012-input-bucket/my-input-artifact.zip", + "type": "S3" + }, + "logs": { + "group-name": "/aws/codebuild/my-sample-project", + "stream-name": "8745a7a9-c340-456a-9166-edf953571bEX", + "deep-link": "https://console.aws.amazon.com/cloudwatch/home?region=us-west-2#logEvent:group=/aws/codebuild/my-sample-project;stream=8745a7a9-c340-456a-9166-edf953571bEX" + }, + "phases": [ + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:12:29 PM", + "duration-in-seconds": 0.0, + "phase-type": "SUBMITTED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 13, 2019 4:12:29 AM", + "duration-in-seconds": 0.0, + "phase-type": "QUEUED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:13:05 PM", + "duration-in-seconds": 36, + "phase-type": "PROVISIONING", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:05 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 4.0, + "phase-type": "DOWNLOAD_SOURCE", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0, + "phase-type": "INSTALL", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0.0, + "phase-type": "PRE_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 70, + "phase-type": "BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0.0, + "phase-type": "POST_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0, + "phase-type": "UPLOAD_ARTIFACTS", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:26 PM", + "duration-in-seconds": 4.0, + "phase-type": "FINALIZING", + "phase-status": "SUCCEEDED" + }, + { + "start-time": "Sep 1, 2017 4:14:26 PM", + "phase-type": "COMPLETED" + } + ] + }, + "completed-phase-status": "SUCCEEDED", + "completed-phase-duration-seconds": 4.0, + "version": "1", + "completed-phase-start": "Sep 1, 2017 4:14:21 PM", + "completed-phase-end": "Sep 1, 2017 4:14:26 PM" + } +} + diff --git a/lambda-events/src/fixtures/example-codebuild-state-change.json b/lambda-events/src/fixtures/example-codebuild-state-change.json new file mode 100644 index 00000000..4bf82f20 --- /dev/null +++ b/lambda-events/src/fixtures/example-codebuild-state-change.json @@ -0,0 +1,142 @@ +{ + "version": "0", + "id": "c030038d-8c4d-6141-9545-00ff7b7153EX", + "detail-type": "CodeBuild Build State Change", + "source": "aws.codebuild", + "account": "123456789012", + "time": "2017-09-01T16:14:28Z", + "region": "us-west-2", + "resources":[ + "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX" + ], + "detail":{ + "build-status": "SUCCEEDED", + "project-name": "my-sample-project", + "build-id": "arn:aws:codebuild:us-west-2:123456789012:build/my-sample-project:8745a7a9-c340-456a-9166-edf953571bEX", + "additional-information": { + "artifact": { + "md5sum": "da9c44c8a9a3cd4b443126e823168fEX", + "sha256sum": "6ccc2ae1df9d155ba83c597051611c42d60e09c6329dcb14a312cecc0a8e39EX", + "location": "arn:aws:s3:::codebuild-123456789012-output-bucket/my-output-artifact.zip" + }, + "environment": { + "image": "aws/codebuild/standard:2.0", + "privileged-mode": false, + "compute-type": "BUILD_GENERAL1_SMALL", + "type": "LINUX_CONTAINER", + "environment-variables": [ + { + "name": "TEST", + "type": "PLAINTEXT", + "value": "TEST" + } + ] + }, + "timeout-in-minutes": 60.0, + "build-complete": true, + "build-number": 55.0, + "initiator": "MyCodeBuildDemoUser", + "build-start-time": "Sep 1, 2017 4:12:29 PM", + "source": { + "location": "codebuild-123456789012-input-bucket/my-input-artifact.zip", + "type": "S3" + }, + "source-version": "my-source-version", + "logs": { + "group-name": "/aws/codebuild/my-sample-project", + "stream-name": "8745a7a9-c340-456a-9166-edf953571bEX", + "deep-link": "https://console.aws.amazon.com/cloudwatch/home?region=us-west-2#logEvent:group=/aws/codebuild/my-sample-project;stream=8745a7a9-c340-456a-9166-edf953571bEX" + }, + "phases": [ + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:12:29 PM", + "duration-in-seconds": 0, + "phase-type": "SUBMITTED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 13, 2019 4:12:29 AM", + "duration-in-seconds": 0.0, + "phase-type": "QUEUED", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:12:29 PM", + "end-time": "Sep 1, 2017 4:13:05 PM", + "duration-in-seconds": 36.0, + "phase-type": "PROVISIONING", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:05 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 4, + "phase-type": "DOWNLOAD_SOURCE", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0.0, + "phase-type": "INSTALL", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:13:10 PM", + "duration-in-seconds": 0, + "phase-type": "PRE_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:13:10 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 70.0, + "phase-type": "BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0, + "phase-type": "POST_BUILD", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:21 PM", + "duration-in-seconds": 0.0, + "phase-type": "UPLOAD_ARTIFACTS", + "phase-status": "SUCCEEDED" + }, + { + "phase-context": [], + "start-time": "Sep 1, 2017 4:14:21 PM", + "end-time": "Sep 1, 2017 4:14:26 PM", + "duration-in-seconds": 4, + "phase-type": "FINALIZING", + "phase-status": "SUCCEEDED" + }, + { + "start-time": "Sep 1, 2017 4:14:26 PM", + "phase-type": "COMPLETED" + } + ] + }, + "current-phase": "COMPLETED", + "current-phase-context": "[]", + "version": "1" + } +} + diff --git a/lambda-events/src/fixtures/example-codedeploy-deployment-event.json b/lambda-events/src/fixtures/example-codedeploy-deployment-event.json new file mode 100644 index 00000000..dc44df09 --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-deployment-event.json @@ -0,0 +1,22 @@ +{ + "account": "123456789012", + "region": "us-east-1", + "detail-type": "CodeDeploy Deployment State-change Notification", + "source": "aws.codedeploy", + "version": "0", + "time": "2016-06-30T22:06:31Z", + "id": "c071bfbf-83c4-49ca-a6ff-3df053957145", + "resources": [ + "arn:aws:codedeploy:us-east-1:123456789012:application:myApplication", + "arn:aws:codedeploy:us-east-1:123456789012:deploymentgroup:myApplication/myDeploymentGroup" + ], + "detail": { + "instanceGroupId": "9fd2fbef-2157-40d8-91e7-6845af69e2d2", + "region": "us-east-1", + "application": "myApplication", + "deploymentId": "d-123456789", + "state": "SUCCESS", + "deploymentGroup": "myDeploymentGroup" + } +} + diff --git a/lambda-events/src/fixtures/example-codedeploy-instance-event.json b/lambda-events/src/fixtures/example-codedeploy-instance-event.json new file mode 100644 index 00000000..001e1d39 --- /dev/null +++ b/lambda-events/src/fixtures/example-codedeploy-instance-event.json @@ -0,0 +1,23 @@ +{ + "account": "123456789012", + "region": "us-east-1", + "detail-type": "CodeDeploy Instance State-change Notification", + "source": "aws.codedeploy", + "version": "0", + "time": "2016-06-30T23:18:50Z", + "id": "fb1d3015-c091-4bf9-95e2-d98521ab2ecb", + "resources": [ + "arn:aws:ec2:us-east-1:123456789012:instance/i-0000000aaaaaaaaaa", + "arn:aws:codedeploy:us-east-1:123456789012:deploymentgroup:myApplication/myDeploymentGroup", + "arn:aws:codedeploy:us-east-1:123456789012:application:myApplication" + ], + "detail": { + "instanceId": "i-0000000aaaaaaaaaa", + "region": "us-east-1", + "state": "SUCCESS", + "application": "myApplication", + "deploymentId": "d-123456789", + "instanceGroupId": "8cd3bfa8-9e72-4cbe-a1e5-da4efc7efd49", + "deploymentGroup": "myDeploymentGroup" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json b/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json new file mode 100644 index 00000000..38f85f00 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-action-execution-stage-change-event.json @@ -0,0 +1,27 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Action Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "execution-id": "01234567-0123-0123-0123-012345678901", + "stage": "Prod", + "action": "myAction", + "state": "STARTED", + "region":"us-west-2", + "type": { + "owner": "AWS", + "category": "Deploy", + "provider": "CodeDeploy", + "version": 1 + } + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json b/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json new file mode 100644 index 00000000..cb311262 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-execution-stage-change-event.json @@ -0,0 +1,18 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Stage Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "state": "STARTED", + "execution-id": "01234567-0123-0123-0123-012345678901" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json b/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json new file mode 100644 index 00000000..43ade4b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline-execution-state-change-event.json @@ -0,0 +1,18 @@ +{ + "version": "0", + "id": "CWE-event-id", + "detail-type": "CodePipeline Pipeline Execution State Change", + "source": "aws.codepipeline", + "account": "123456789012", + "time": "2017-04-22T03:31:47Z", + "region": "us-east-1", + "resources": [ + "arn:aws:codepipeline:us-east-1:123456789012:pipeline:myPipeline" + ], + "detail": { + "pipeline": "myPipeline", + "version": 1, + "state": "STARTED", + "execution-id": "01234567-0123-0123-0123-012345678901" + } +} diff --git a/lambda-events/src/fixtures/example-codepipeline_job-event.json b/lambda-events/src/fixtures/example-codepipeline_job-event.json new file mode 100644 index 00000000..d43d2d0f --- /dev/null +++ b/lambda-events/src/fixtures/example-codepipeline_job-event.json @@ -0,0 +1,34 @@ +{ + "CodePipeline.job": { + "id": "11111111-abcd-1111-abcd-111111abcdef", + "accountId": "111111111111", + "data": { + "actionConfiguration": { + "configuration": { + "FunctionName": "MyLambdaFunctionForAWSCodePipeline", + "UserParameters": "some-input-such-as-a-URL" + } + }, + "inputArtifacts": [ + { + "location": { + "s3Location": { + "bucketName": "the name of the bucket configured as the pipeline artifact store in Amazon S3, for example codepipeline-us-east-2-1234567890", + "objectKey": "the name of the application, for example CodePipelineDemoApplication.zip" + }, + "type": "S3" + }, + "revision": null, + "name": "ArtifactName" + } + ], + "outputArtifacts": [], + "artifactCredentials": { + "secretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY", + "sessionToken": "MIICiTCCAfICCQD6m7oRw0uXOjANBgkqhkiG9w0BAQUFADCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wHhcNMTEwNDI1MjA0NTIxWhcNMTIwNDI0MjA0NTIxWjCBiDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAldBMRAwDgYDVQQHEwdTZWF0dGxlMQ8wDQYDVQQKEwZBbWF6b24xFDASBgNVBAsTC0lBTSBDb25zb2xlMRIwEAYDVQQDEwlUZXN0Q2lsYWMxHzAdBgkqhkiG9w0BCQEWEG5vb25lQGFtYXpvbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMaK0dn+a4GmWIWJ21uUSfwfEvySWtC2XADZ4nB+BLYgVIk60CpiwsZ3G93vUEIO3IyNoH/f0wYK8m9TrDHudUZg3qX4waLG5M43q7Wgc/MbQITxOUSQv7c7ugFFDzQGBzZswY6786m86gpEIbb3OhjZnzcvQAaRHhdlQWIMm2nrAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAtCu4nUhVVxYUntneD9+h8Mg9q6q+auNKyExzyLwaxlAoo7TJHidbtS4J5iNmZgXL0FkbFFBjvSfpJIlJ00zbhNYS5f6GuoEDmFJl0ZxBHjJnyp378OD8uTs7fLvjx79LjSTbNYiytVbZPQUQ5Yaxu2jXnimvw3rrszlaEXAMPLE=", + "accessKeyId": "AKIAIOSFODNN7EXAMPLE" + }, + "continuationToken": "A continuation token if continuing job" + } + } +} 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 new file mode 100644 index 00000000..99acf0a2 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-create-auth-challenge.json @@ -0,0 +1,40 @@ +{ + "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" + } + }, + "response": { + "publicChallengeParameters": { + "a": "b" + }, + "privateChallengeParameters": { + "c": "d" + }, + "challengeMetadata": "challengeMetadata" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json new file mode 100644 index 00000000..90e8b68e --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-custommessage.json @@ -0,0 +1,28 @@ +{ + "version": "1", + "triggerSource": "CustomMessage_SignUp/CustomMessage_ResendCode/CustomMessage_ForgotPassword/CustomMessage_VerifyUserAttribute", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "phone_number_verified": true, + "email_verified": false + }, + "codeParameter": "####", + "usernameParameter": "{username}", + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "smsMessage": "", + "emailMessage": "", + "emailSubject": "" + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json new file mode 100644 index 00000000..c878e661 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge-optional-response-fields.json @@ -0,0 +1,34 @@ +{ + "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": false + }, + "response": { + "challengeName": "challengeName" + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.json new file mode 100644 index 00000000..33106f88 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-define-auth-challenge.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": false + }, + "response": { + "challengeName": "challengeName", + "issueTokens": true, + "failAuthentication": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json b/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json new file mode 100644 index 00000000..958b40c3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-migrateuser.json @@ -0,0 +1,34 @@ +{ + "version": "1", + "triggerSource": "UserMigration_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "password": "", + "validationData": { + "exampleMetadataKey": "example metadata value" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "userAttributes": { + "email": "", + "phone_number": "" + }, + "finalUserStatus": "", + "messageAction": "", + "desiredDeliveryMediums": [ + "", + "" + ], + "forceAliasCreation": true + } +} + diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json b/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json new file mode 100644 index 00000000..1af00a20 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-postauthentication.json @@ -0,0 +1,22 @@ +{ + "version": "1", + "triggerSource": "PostAuthentication_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "newDeviceUsed": true, + "userAttributes" : { + "email": "", + "phone_number": "" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json b/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json new file mode 100644 index 00000000..c4e4aa18 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-postconfirmation.json @@ -0,0 +1,21 @@ +{ + "version": "1", + "triggerSource": "PostConfirmation_ConfirmSignUp", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes" : { + "email": "", + "phone_number": "" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json b/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json new file mode 100644 index 00000000..45e76bff --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-preauthentication.json @@ -0,0 +1,21 @@ +{ + "version": "1", + "triggerSource": "PreAuthentication_Authentication", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "email": "" + }, + "validationData": { + "k1": "v1", + "k2": "v2" + } + }, + "response": {} +} diff --git a/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json b/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json new file mode 100644 index 00000000..e579e3b6 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-presignup.json @@ -0,0 +1,29 @@ +{ + "version": "1", + "triggerSource": "PreSignUp_SignUp", + "region": "", + "userPoolId": "", + "userName": "", + "callerContext": { + "awsSdkVersion": "", + "clientId": "" + }, + "request": { + "userAttributes": { + "email": "", + "phone_number": "" + }, + "validationData": { + "k1": "v1", + "k2": "v2" + }, + "clientMetadata": { + "exampleMetadataKey": "example metadata value" + } + }, + "response": { + "autoConfirmUser": false, + "autoVerifyEmail": true, + "autoVerifyPhone": true + } +} 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 new file mode 100644 index 00000000..fed10a51 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen-incoming.json @@ -0,0 +1,29 @@ +{ + "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" + }, + "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.json b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json new file mode 100644 index 00000000..7b851904 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-pretokengen.json @@ -0,0 +1,40 @@ +{ + "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" + }, + "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": { + "claimsToAddOrOverride": { + "attribute_key2": "attribute_value2", + "attribute_key": "attribute_value" + }, + "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"], + "preferredRole": "arn:aws:iam::XXXXXXXXXXX:role/sns_caller" + } + } + } +} + 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 new file mode 100644 index 00000000..70a973f4 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge-optional-answer-correct.json @@ -0,0 +1,29 @@ +{ + "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" + } + }, + "response": { + } +} 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 new file mode 100644 index 00000000..b1d88fee --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event-userpools-verify-auth-challenge.json @@ -0,0 +1,30 @@ +{ + "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" + } + }, + "response": { + "answerCorrect": true + } +} diff --git a/lambda-events/src/fixtures/example-cognito-event.json b/lambda-events/src/fixtures/example-cognito-event.json new file mode 100644 index 00000000..eec738d3 --- /dev/null +++ b/lambda-events/src/fixtures/example-cognito-event.json @@ -0,0 +1,17 @@ +{ + "datasetName": "datasetName", + "eventType": "SyncTrigger", + "region": "us-east-1", + "identityId": "identityId", + "datasetRecords": + { + "SampleKey1": + { + "newValue": "newValue1", + "oldValue": "oldValue1", + "op": "replace" + } + }, + "identityPoolId": "identityPoolId", + "version": 2 +} diff --git a/lambda-events/src/fixtures/example-config-event.json b/lambda-events/src/fixtures/example-config-event.json new file mode 100644 index 00000000..e269c762 --- /dev/null +++ b/lambda-events/src/fixtures/example-config-event.json @@ -0,0 +1,13 @@ +{ + "configRuleId": "config-rule-0123456", + "version": "1.0", + "configRuleName": "periodic-config-rule", + "configRuleArn": "arn:aws:config:us-east-1:012345678912:config-rule/config-rule-0123456", + "invokingEvent": "{\"configSnapshotId\":\"00000000-0000-0000-0000-000000000000\",\"s3ObjectKey\":\"AWSLogs/000000000000/Config/us-east-1/2016/2/24/ConfigSnapshot/000000000000_Config_us-east-1_ConfigSnapshot_20160224T182319Z_00000000-0000-0000-0000-000000000000.json.gz\",\"s3Bucket\":\"config-bucket\",\"notificationCreationTime\":\"2016-02-24T18:23:20.328Z\",\"messageType\":\"ConfigurationSnapshotDeliveryCompleted\",\"recordVersion\":\"1.1\"}", + "resultToken": "myResultToken", + "eventLeftScope": false, + "ruleParameters": "{\"myParameterKey\":\"myParameterValue\"}", + "executionRoleArn": "arn:aws:iam::012345678912:role/config-role", + "accountId": "012345678912" +} + diff --git a/lambda-events/src/fixtures/example-connect-event-without-queue.json b/lambda-events/src/fixtures/example-connect-event-without-queue.json new file mode 100644 index 00000000..c3d626db --- /dev/null +++ b/lambda-events/src/fixtures/example-connect-event-without-queue.json @@ -0,0 +1,29 @@ +{ + "Name": "ContactFlowExecution", + "Details": { + "Parameters": { + "key1": "value1", + "key2": "value2" + }, + "ContactData": { + "ContactId": "ASDAcxcasDFSSDFs", + "InitialContactId": "Acxsada-asdasdaxA", + "PreviousContactId": "Acxsada-asdasdaxA", + "Channel": "Voice", + "InstanceARN": "", + "InitiationMethod": "INBOUND/OUTBOUND/TRANSFER/CALLBACK", + "SystemEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "01234567" + }, + "CustomerEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "+14802021091" + }, + "Attributes": { + "key1": "value", + "key2": "value" + } + } + } +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-connect-event.json b/lambda-events/src/fixtures/example-connect-event.json new file mode 100644 index 00000000..2480de70 --- /dev/null +++ b/lambda-events/src/fixtures/example-connect-event.json @@ -0,0 +1,33 @@ +{ + "Name": "ContactFlowExecution", + "Details": { + "Parameters": { + "key1": "value1", + "key2": "value2" + }, + "ContactData": { + "ContactId": "ASDAcxcasDFSSDFs", + "InitialContactId": "Acxsada-asdasdaxA", + "PreviousContactId": "Acxsada-asdasdaxA", + "Channel": "Voice", + "InstanceARN": "", + "InitiationMethod": "INBOUND/OUTBOUND/TRANSFER/CALLBACK", + "SystemEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "01234567" + }, + "CustomerEndpoint": { + "Type": "TELEPHONE_NUMBER", + "Address": "+14802021091" + }, + "Queue": { + "Name": "PrimaryPhoneQueue", + "ARN": "" + }, + "Attributes": { + "key1": "value", + "key2": "value" + } + } + } +} \ No newline at end of file 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 new file mode 100644 index 00000000..873cccce --- /dev/null +++ b/lambda-events/src/fixtures/example-dynamodb-event-record-with-optional-fields.json @@ -0,0 +1,26 @@ +{ + "awsRegion":"eu-west-1", + "eventID":"00000000-0000-0000-0000-000000000000", + "eventName":"INSERT", + "userIdentity":null, + "recordFormat":"application/json", + "tableName":"examples", + "dynamodb":{ + "ApproximateCreationDateTime":1649809356015, + "Keys":{ + "id":{ + "S":"00000000-0000-0000-0000-000000000000" + } + }, + "NewImage":{ + "id":{ + "S":"00000000-0000-0000-0000-000000000000" + }, + "created":{ + "S":"2022-02-16T15:12:00.14Z" + } + }, + "SizeBytes":292 + }, + "eventSource":"aws:dynamodb" +} diff --git a/lambda-events/src/fixtures/example-dynamodb-event.json b/lambda-events/src/fixtures/example-dynamodb-event.json new file mode 100644 index 00000000..46a17695 --- /dev/null +++ b/lambda-events/src/fixtures/example-dynamodb-event.json @@ -0,0 +1,153 @@ +{ + "Records": [ + { + "eventID": "f07f8ca4b0b26cb9c4e5e77e69f274ee", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "userIdentity":{ + "type":"Service", + "principalId":"dynamodb.amazonaws.com" + }, + "dynamodb": { + "ApproximateCreationDateTime": 1480642020, + "Keys": { + "val": { + "S": "data" + }, + "key": { + "S": "binary" + } + }, + "NewImage": { + "val": { + "S": "data" + }, + "asdf1": { + "B": "AAEqQQ==" + }, + "asdf2": { + "BS": [ + "AAEqQQ==", + "QSoBAA==" + ] + }, + "key": { + "S": "binary" + } + }, + "SequenceNumber": "1405400000000002063282832", + "SizeBytes": 54, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000" + }, + { + "eventID": "f07f8ca4b0b26cb9c4e5e77e42f274ee", + "eventName": "INSERT", + "eventVersion": "1.1", + "eventSource": "aws:dynamodb", + "awsRegion": "us-east-1", + "dynamodb": { + "ApproximateCreationDateTime": 1480642020, + "Keys": { + "val": { + "S": "data" + }, + "key": { + "S": "binary" + } + }, + "NewImage": { + "val": { + "S": "data" + }, + "asdf1": { + "B": "AAEqQQ==" + }, + "b2": { + "B": "test" + }, + "asdf2": { + "BS": [ + "AAEqQQ==", + "QSoBAA==", + "AAEqQQ==" + ] + }, + "key": { + "S": "binary" + }, + "Binary": { + "B": "AAEqQQ==" + }, + "Boolean": { + "BOOL": true + }, + "BinarySet": { + "BS": [ + "AAEqQQ==", + "AAEqQQ==" + ] + }, + "List": { + "L": [ + { + "S": "Cookies" + }, + { + "S": "Coffee" + }, + { + "N": "3.14159" + } + ] + }, + "Map": { + "M": { + "Name": { + "S": "Joe" + }, + "Age": { + "N": "35" + } + } + }, + "FloatNumber": { + "N": "123.45" + }, + "IntegerNumber": { + "N": "123" + }, + "NumberSet": { + "NS": [ + "1234", + "567.8" + ] + }, + "Null": { + "NULL": true + }, + "String": { + "S": "Hello" + }, + "StringSet": { + "SS": [ + "Giraffe", + "Zebra" + ] + }, + "EmptyStringSet": { + "SS": [] + } + }, + "SequenceNumber": "1405400000000002063282832", + "SizeBytes": 54, + "StreamViewType": "NEW_AND_OLD_IMAGES" + }, + "eventSourceARN": "arn:aws:dynamodb:us-east-1:123456789012:table/Example-Table/stream/2016-12-01T00:00:00.000" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-ecr-image-scan-event.json b/lambda-events/src/fixtures/example-ecr-image-scan-event.json new file mode 100644 index 00000000..219b13a3 --- /dev/null +++ b/lambda-events/src/fixtures/example-ecr-image-scan-event.json @@ -0,0 +1,24 @@ +{ + "version": "0", + "id": "01234567-0123-0123-0123-012345678901", + "detail-type": "ECR Image Scan", + "source": "aws.ecr", + "account": "123456789012", + "time": "2019-10-30T21:32:27Z", + "region": "eu-north-1", + "resources": ["arn:aws:ecr:eu-north-1:123456789012:repository/tribble-image-scan-test"], + "detail": { + "scan-status": "COMPLETE", + "repository-name": "tribble-image-scan-test", + "finding-severity-counts": { + "CRITICAL": 10, + "HIGH": 2, + "MEDIUM": 9, + "LOW": 3, + "INFORMATIONAL": 0, + "UNDEFINED": 0 + }, + "image-digest": "sha256:d4a96ee9443e641fc100e763a0c10928720b50c6e3ea3342d05d7c3435fc5355", + "image-tags": ["1572471135"] + } +} diff --git a/lambda-events/src/fixtures/example-firehose-event.json b/lambda-events/src/fixtures/example-firehose-event.json new file mode 100644 index 00000000..8874a568 --- /dev/null +++ b/lambda-events/src/fixtures/example-firehose-event.json @@ -0,0 +1,33 @@ +{ + "invocationId": "invoked123", + "deliveryStreamArn": "aws:lambda:events", + "sourceKinesisStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/test", + "region": "us-west-2", + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000000", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c317a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "subsequenceNumber": 123456 + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000001", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c318a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785155", + "subsequenceNumber": 123457 + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-iot-custom-auth-request.json b/lambda-events/src/fixtures/example-iot-custom-auth-request.json new file mode 100644 index 00000000..582c0e23 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot-custom-auth-request.json @@ -0,0 +1,25 @@ +{ + "token" :"aToken", + "signatureVerified": true, + "protocols": ["tls", "http", "mqtt"], + "protocolData": { + "tls" : { + "serverName": "serverName" + }, + "http": { + "headers": { + "X-Request-ID": "abc123" + }, + "queryString": "?foo=bar" + }, + "mqtt": { + "username": "myUserName", + "password": "bXlQYXNzd29yZA==", + "clientId": "myClientId" + } + }, + "connectionMetadata": { + "id": "e56f08c3-c559-490f-aa9f-7e8427d0f57b" + } +} + diff --git a/lambda-events/src/fixtures/example-iot-custom-auth-response.json b/lambda-events/src/fixtures/example-iot-custom-auth-response.json new file mode 100644 index 00000000..61983975 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot-custom-auth-response.json @@ -0,0 +1,19 @@ +{ + "isAuthenticated":true, + "principalId": "xxxxxxxx", + "disconnectAfterInSeconds": 86400, + "refreshAfterInSeconds": 300, + "policyDocuments": [ + { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["iot:Publish"], + "Effect": "Allow", + "Resource": ["arn:aws:iot:us-east-1::topic/customauthtesting"] + } + ] + } + ] +} + diff --git a/lambda-events/src/fixtures/example-iot_1_click-event.json b/lambda-events/src/fixtures/example-iot_1_click-event.json new file mode 100644 index 00000000..1f7eefea --- /dev/null +++ b/lambda-events/src/fixtures/example-iot_1_click-event.json @@ -0,0 +1,30 @@ +{ + "deviceEvent": { + "buttonClicked": { + "clickType": "SINGLE", + "reportedTime": "2018-05-04T23:26:33.747Z" + } + }, + "deviceInfo": { + "attributes": { + "key3": "value3", + "key1": "value1", + "key4": "value4" + }, + "type": "button", + "deviceId": "G030PMXXXXXXXXXX", + "remainingLife": 5.00 + }, + "placementInfo": { + "projectName": "test", + "placementName": "myPlacement", + "attributes": { + "location": "Seattle", + "equipment": "printer" + }, + "devices": { + "myButton": "G030PMXXXXXXXXXX" + } + } +} + diff --git a/lambda-events/src/fixtures/example-iot_button-event.json b/lambda-events/src/fixtures/example-iot_button-event.json new file mode 100644 index 00000000..1ffcef52 --- /dev/null +++ b/lambda-events/src/fixtures/example-iot_button-event.json @@ -0,0 +1,5 @@ +{ + "serialNumber": "ABCDEFG12345", + "clickType": "SINGLE", + "batteryVoltage": "2000 mV" +} diff --git a/lambda-events/src/fixtures/example-kafka-event.json b/lambda-events/src/fixtures/example-kafka-event.json new file mode 100644 index 00000000..97491c88 --- /dev/null +++ b/lambda-events/src/fixtures/example-kafka-event.json @@ -0,0 +1,24 @@ +{ + "eventSource": "aws:kafka", + "eventSourceArn": "arn:aws:kafka:us-west-2:012345678901:cluster/ExampleMSKCluster/e9f754c6-d29a-4430-a7db-958a19fd2c54-4", + "bootstrapServers": "b-2.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092,b-1.demo-cluster-1.a1bcde.c1.kafka.us-east-1.amazonaws.com:9092", + "records": { + "AWSKafkaTopic-0": [ + { + "topic": "AWSKafkaTopic", + "partition": 0, + "offset": 0, + "timestamp": 1595035749700, + "timestampType": "CREATE_TIME", + "key": "OGQ1NTk2YjQtMTgxMy00MjM4LWIyNGItNmRhZDhlM2QxYzBj", + "value": "OGQ1NTk2YjQtMTgxMy00MjM4LWIyNGItNmRhZDhlM2QxYzBj", + "headers": [ + { + "headerKey": [104, 101, 97, 100, 101, 114, 86, 97, 108, 117, 101] + } + ] + } + ] + } + } + diff --git a/lambda-events/src/fixtures/example-kinesis-event.json b/lambda-events/src/fixtures/example-kinesis-event.json new file mode 100644 index 00000000..be20b6f2 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-event.json @@ -0,0 +1,36 @@ +{ + "Records": [ + { + "kinesis": { + "kinesisSchemaVersion": "1.0", + "partitionKey": "s1", + "sequenceNumber": "49568167373333333333333333333333333333333333333333333333", + "data": "SGVsbG8gV29ybGQ=", + "approximateArrivalTimestamp": 1480641523.477 + }, + "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" + } + ] +} diff --git a/lambda-events/src/fixtures/example-kinesis-firehose-event.json b/lambda-events/src/fixtures/example-kinesis-firehose-event.json new file mode 100644 index 00000000..8874a568 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-firehose-event.json @@ -0,0 +1,33 @@ +{ + "invocationId": "invoked123", + "deliveryStreamArn": "aws:lambda:events", + "sourceKinesisStreamArn": "arn:aws:kinesis:us-east-1:123456789012:stream/test", + "region": "us-west-2", + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000000", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c317a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785154", + "subsequenceNumber": 123456 + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "approximateArrivalTimestamp": 1507217624302, + "kinesisRecordMetadata": { + "shardId": "shardId-000000000001", + "partitionKey": "4d1ad2b9-24f8-4b9d-a088-76e9947c318a", + "approximateArrivalTimestamp": 1507217624302, + "sequenceNumber": "49546986683135544286507457936321625675700192471156785155", + "subsequenceNumber": 123457 + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-kinesis-firehose-response.json b/lambda-events/src/fixtures/example-kinesis-firehose-response.json new file mode 100644 index 00000000..31950770 --- /dev/null +++ b/lambda-events/src/fixtures/example-kinesis-firehose-response.json @@ -0,0 +1,32 @@ +{ + "records": [ + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record1", + "result": "TRANSFORMED_STATE_OK", + "metadata": { + "partitionKeys": {} + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record2", + "result": "TRANSFORMED_STATE_DROPPED", + "metadata": { + "partitionKeys": {} + } + }, + { + "data": "SGVsbG8gV29ybGQ=", + "recordId": "record3", + "result": "TransformedStateOk", + "metadata": { + "partitionKeys": { + "iamKey1": "iamValue1", + "iamKey2": "iamValue2" + } + } + } + ] + } + diff --git a/lambda-events/src/fixtures/example-lex-event.json b/lambda-events/src/fixtures/example-lex-event.json new file mode 100644 index 00000000..ae995d9a --- /dev/null +++ b/lambda-events/src/fixtures/example-lex-event.json @@ -0,0 +1,44 @@ +{ + "currentIntent": { + "name": "intent-name", + "slots": { + "slot name1": "value1", + "slot name2": "value2" + }, + "slotDetails": { + "slot name1": { + "resolutions": [ + { "value1": "resolved value1" }, + { "value2": "resolved value2" } + ], + "originalValue": "original text" + }, + "slot name2": { + "resolutions": [ + { "value1": "resolved value1" }, + { "value2": "resolved value2" } + ], + "originalValue": "original text" + } + }, + "confirmationStatus": "None, Confirmed, or Denied (intent confirmation, if configured)" + }, + "bot": { + "name": "bot name", + "alias": "bot alias", + "version": "bot version" + }, + "userId": "User ID specified in the POST request to Amazon Lex.", + "inputTranscript": "Text used to process the request", + "invocationSource": "FulfillmentCodeHook or DialogCodeHook", + "outputDialogMode": "Text or Voice, based on ContentType request header in runtime API request", + "messageVersion": "1.0", + "sessionAttributes": { + "key1": "value1", + "key2": "value2" + }, + "requestAttributes": { + "key1": "value1", + "key2": "value2" + } +} diff --git a/lambda-events/src/fixtures/example-lex-response.json b/lambda-events/src/fixtures/example-lex-response.json new file mode 100644 index 00000000..b83cea2e --- /dev/null +++ b/lambda-events/src/fixtures/example-lex-response.json @@ -0,0 +1,40 @@ +{ + "sessionAttributes": { + "key1": "value1", + "key2": "value2" + }, + "dialogAction": { + "type": "ElicitIntent, ElicitSlot, ConfirmIntent, Delegate, or Close", + "fulfillmentState": "Fulfilled or Failed", + "message": { + "contentType": "PlainText or SSML", + "content": "message to convey to the user" + }, + "intentName": "intent-name", + "slots": { + "slot-name1": "value1", + "slot-name2": "value2", + "slot-name3": "value3" + }, + "slotToElicit": "slot-name", + "responseCard": { + "version": 3, + "contentType": "application/vnd.amazonaws.card.generic", + "genericAttachments": [ + { + "title": "card-title", + "subTitle": "card-sub-title", + "imageUrl": "URL of the image to be shown", + "attachmentLinkUrl": "URL of the attachment to be associated with the card", + "buttons": [ + { + "text": "button-text", + "value": "value sent to server on button click" + } + ] + } + ] + } + } +} + diff --git a/lambda-events/src/fixtures/example-rabbitmq-event.json b/lambda-events/src/fixtures/example-rabbitmq-event.json new file mode 100644 index 00000000..49a8b5ef --- /dev/null +++ b/lambda-events/src/fixtures/example-rabbitmq-event.json @@ -0,0 +1,52 @@ +{ + "eventSource": "aws:rmq", + "eventSourceArn": "arn:aws:mq:us-west-2:112556298976:broker:test:b-9bcfa592-423a-4942-879d-eb284b418fc8", + "rmqMessagesByQueue": { + "test::/": [ + { + "basicProperties": { + "contentType": "text/plain", + "contentEncoding": null, + "headers": { + "header1": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 49 + ] + }, + "header2": { + "bytes": [ + 118, + 97, + 108, + 117, + 101, + 50 + ] + }, + "numberInHeader": 10 + }, + "deliveryMode": 1, + "priority": 34, + "correlationId": null, + "replyTo": null, + "expiration": "60000", + "messageId": null, + "timestamp": "Jan 1, 1970, 12:33:41 AM", + "type": null, + "userId": "AIDACKCEVSQ6C2EXAMPLE", + "appId": null, + "clusterId": null, + "bodySize": 80 + }, + "redelivered": false, + "data": "eyJ0aW1lb3V0IjowLCJkYXRhIjoiQ1pybWYwR3c4T3Y0YnFMUXhENEUifQ==" + } + ] + } +} + diff --git a/lambda-events/src/fixtures/example-s3-event-with-decoded.json b/lambda-events/src/fixtures/example-s3-event-with-decoded.json new file mode 100644 index 00000000..a9c21c8c --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-event-with-decoded.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.123Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "C3D13FE58DE4C810", + "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "sourcebucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::mybucket" + }, + "object": { + "key": "Happy%20Face.jpg", + "urlDecodedKey": "Happy Face.jpg", + "size": 1024, + "versionId": "version", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "Happy Sequencer" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-s3-event.json b/lambda-events/src/fixtures/example-s3-event.json new file mode 100644 index 00000000..d35f8a3d --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-event.json @@ -0,0 +1,40 @@ +{ + "Records": [ + { + "eventVersion": "2.0", + "eventSource": "aws:s3", + "awsRegion": "us-east-1", + "eventTime": "1970-01-01T00:00:00.123Z", + "eventName": "ObjectCreated:Put", + "userIdentity": { + "principalId": "EXAMPLE" + }, + "requestParameters": { + "sourceIPAddress": "127.0.0.1" + }, + "responseElements": { + "x-amz-request-id": "C3D13FE58DE4C810", + "x-amz-id-2": "FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD" + }, + "s3": { + "s3SchemaVersion": "1.0", + "configurationId": "testConfigRule", + "bucket": { + "name": "sourcebucket", + "ownerIdentity": { + "principalId": "EXAMPLE" + }, + "arn": "arn:aws:s3:::mybucket" + }, + "object": { + "key": "Happy%20Face.jpg", + "size": 1024, + "versionId": "version", + "eTag": "d41d8cd98f00b204e9800998ecf8427e", + "sequencer": "Happy Sequencer" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json new file mode 100644 index 00000000..34aa55b1 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-assumed-role.json @@ -0,0 +1,42 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "AssumedRole", + "principalId": "principalId", + "arn": "arn:aws:sts::111122223333:assumed-role/Admin/example", + "accountId": "111122223333", + "accessKeyId": "accessKeyId", + "sessionContext": { + "attributes": { + "mfaAuthenticated": "false", + "creationDate": "Wed Mar 10 23:41:52 UTC 2021" + }, + "sessionIssuer": { + "type": "Role", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:role/Admin", + "accountId": "111122223333", + "userName": "Admin" + } + } + }, + "protocolVersion": "1.00" +} \ No newline at end of file diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json new file mode 100644 index 00000000..81b0ec71 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-get-object-iam.json @@ -0,0 +1,29 @@ +{ + "xAmzRequestId": "requestId", + "getObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=", + "outputRoute": "io-use1-001", + "outputToken": "OutputToken" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.00" +} diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json new file mode 100644 index 00000000..ec920871 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-head-object-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "headObjectContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json new file mode 100644 index 00000000..e1c6692c --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsContext": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json new file mode 100644 index 00000000..715dffa0 --- /dev/null +++ b/lambda-events/src/fixtures/example-s3-object-lambda-event-list-objects-v2-iam.json @@ -0,0 +1,28 @@ +{ + "xAmzRequestId": "requestId", + "listObjectsV2Context": { + "inputS3Url": "https://my-s3-ap-111122223333.s3-accesspoint.us-east-1.amazonaws.com/example?X-Amz-Security-Token=" + }, + "configuration": { + "accessPointArn": "arn:aws:s3-object-lambda:us-east-1:111122223333:accesspoint/example-object-lambda-ap", + "supportingAccessPointArn": "arn:aws:s3:us-east-1:111122223333:accesspoint/example-ap", + "payload": "{}" + }, + "userRequest": { + "url": "https://object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com/example", + "headers": { + "Host": "object-lambda-111122223333.s3-object-lambda.us-east-1.amazonaws.com", + "Accept-Encoding": "identity", + "X-Amz-Content-SHA256": "e3b0c44298fc1example" + } + }, + "userIdentity": { + "type": "IAMUser", + "principalId": "principalId", + "arn": "arn:aws:iam::111122223333:user/username", + "accountId": "111122223333", + "accessKeyId": "accessKeyId" + }, + "protocolVersion": "1.01" +} + diff --git a/lambda-events/src/fixtures/example-ses-event.json b/lambda-events/src/fixtures/example-ses-event.json new file mode 100644 index 00000000..77bee26a --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-event.json @@ -0,0 +1,101 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "mail": { + "commonHeaders": { + "from": [ + "Amazon Web Services " + ], + "to": [ + "lambda@amazon.com" + ], + "returnPath": "aws@amazon.com", + "messageId": "", + "date": "Mon, 5 Dec 2016 18:40:08 -0800", + "subject": "Test Subject" + }, + "source": "aws@amazon.com", + "timestamp": "1970-01-01T00:00:00.123Z", + "destination": [ + "lambda@amazon.com" + ], + "headers": [ + { + "name": "Return-Path", + "value": "" + }, + { + "name": "Received", + "value": "from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY=" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "From", + "value": "Amazon Web Services " + }, + { + "name": "Date", + "value": "Mon, 5 Dec 2016 18:40:08 -0800" + }, + { + "name": "Message-ID", + "value": "" + }, + { + "name": "Subject", + "value": "Test Subject" + }, + { + "name": "To", + "value": "lambda@amazon.com" + }, + { + "name": "Content-Type", + "value": "multipart/alternative; boundary=94eb2c0742269658b10542f452a9" + } + ], + "headersTruncated": false, + "messageId": "6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1" + }, + "receipt": { + "recipients": [ + "lambda@amazon.com" + ], + "timestamp": "1970-01-01T00:00:00.123Z", + "spamVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "processingTimeMillis": 574, + "action": { + "type": "Lambda", + "invocationType": "Event", + "functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function" + }, + "spfVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + } + } + }, + "eventSource": "aws:ses" + } + ] +} diff --git a/lambda-events/src/fixtures/example-ses-lambda-event.json b/lambda-events/src/fixtures/example-ses-lambda-event.json new file mode 100644 index 00000000..77bee26a --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-lambda-event.json @@ -0,0 +1,101 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "mail": { + "commonHeaders": { + "from": [ + "Amazon Web Services " + ], + "to": [ + "lambda@amazon.com" + ], + "returnPath": "aws@amazon.com", + "messageId": "", + "date": "Mon, 5 Dec 2016 18:40:08 -0800", + "subject": "Test Subject" + }, + "source": "aws@amazon.com", + "timestamp": "1970-01-01T00:00:00.123Z", + "destination": [ + "lambda@amazon.com" + ], + "headers": [ + { + "name": "Return-Path", + "value": "" + }, + { + "name": "Received", + "value": "from mx.amazon.com (mx.amazon.com [127.0.0.1]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id 6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1 for lambda@amazon.com; Tue, 06 Dec 2016 02:40:10 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=iatn.net; s=amazon; h=mime-version:from:date:message-id:subject:to; bh=chlJxa/vZ11+0O9lf4tKDM/CcPjup2nhhdITm+hSf3c=; b=SsoNPK0wX7umtWnw8pln3YSib+E09XO99d704QdSc1TR1HxM0OTti/UaFxVD4e5b0+okBqo3rgVeWgNZ0sWZEUhBaZwSL3kTd/nHkcPexeV0XZqEgms1vmbg75F6vlz9igWflO3GbXyTRBNMM0gUXKU/686hpVW6aryEIfM/rLY=" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "From", + "value": "Amazon Web Services " + }, + { + "name": "Date", + "value": "Mon, 5 Dec 2016 18:40:08 -0800" + }, + { + "name": "Message-ID", + "value": "" + }, + { + "name": "Subject", + "value": "Test Subject" + }, + { + "name": "To", + "value": "lambda@amazon.com" + }, + { + "name": "Content-Type", + "value": "multipart/alternative; boundary=94eb2c0742269658b10542f452a9" + } + ], + "headersTruncated": false, + "messageId": "6n4thuhcbhpfiuf25gshf70rss364fuejrvmqko1" + }, + "receipt": { + "recipients": [ + "lambda@amazon.com" + ], + "timestamp": "1970-01-01T00:00:00.123Z", + "spamVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "processingTimeMillis": 574, + "action": { + "type": "Lambda", + "invocationType": "Event", + "functionArn": "arn:aws:lambda:us-east-1:000000000000:function:my-ses-lambda-function" + }, + "spfVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + } + } + }, + "eventSource": "aws:ses" + } + ] +} diff --git a/lambda-events/src/fixtures/example-ses-s3-event.json b/lambda-events/src/fixtures/example-ses-s3-event.json new file mode 100644 index 00000000..1f2deb2e --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-s3-event.json @@ -0,0 +1,115 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "receipt": { + "timestamp": "2015-09-11T20:32:33.936Z", + "processingTimeMillis": 406, + "recipients": [ + "recipient@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "action": { + "type": "S3", + "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic", + "bucketName": "my-S3-bucket", + "objectKey": "email" + } + }, + "mail": { + "timestamp": "2015-09-11T20:32:33.936Z", + "source": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "messageId": "d6iitobk75ur44p8kdnnp7g2n800", + "destination": [ + "recipient@example.com" + ], + "headersTruncated": false, + "headers": [ + { + "name": "Return-Path", + "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" + }, + { + "name": "Received", + "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" + }, + { + "name": "From", + "value": "sender@example.com" + }, + { + "name": "To", + "value": "recipient@example.com" + }, + { + "name": "Subject", + "value": "Example subject" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "7bit" + }, + { + "name": "Date", + "value": "Fri, 11 Sep 2015 20:32:32 +0000" + }, + { + "name": "Message-ID", + "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" + }, + { + "name": "X-SES-Outgoing", + "value": "2015.09.11-54.240.9.183" + }, + { + "name": "Feedback-ID", + "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" + } + ], + "commonHeaders": { + "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "from": [ + "sender@example.com" + ], + "date": "Fri, 11 Sep 2015 20:32:32 +0000", + "to": [ + "recipient@example.com" + ], + "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", + "subject": "Example subject" + } + } + }, + "eventSource": "aws:ses" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-ses-sns-event.json b/lambda-events/src/fixtures/example-ses-sns-event.json new file mode 100644 index 00000000..bfededc1 --- /dev/null +++ b/lambda-events/src/fixtures/example-ses-sns-event.json @@ -0,0 +1,113 @@ +{ + "Records": [ + { + "eventVersion": "1.0", + "ses": { + "receipt": { + "timestamp": "2015-09-11T20:32:33.936Z", + "processingTimeMillis": 222, + "recipients": [ + "recipient@example.com" + ], + "spamVerdict": { + "status": "PASS" + }, + "virusVerdict": { + "status": "PASS" + }, + "spfVerdict": { + "status": "PASS" + }, + "dkimVerdict": { + "status": "PASS" + }, + "dmarcVerdict": { + "status": "PASS" + }, + "dmarcPolicy": "reject", + "action": { + "type": "SNS", + "topicArn": "arn:aws:sns:us-east-1:012345678912:example-topic" + } + }, + "mail": { + "timestamp": "2015-09-11T20:32:33.936Z", + "source": "61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com", + "messageId": "d6iitobk75ur44p8kdnnp7g2n800", + "destination": [ + "recipient@example.com" + ], + "headersTruncated": false, + "headers": [ + { + "name": "Return-Path", + "value": "<0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com>" + }, + { + "name": "Received", + "value": "from a9-183.smtp-out.amazonses.com (a9-183.smtp-out.amazonses.com [54.240.9.183]) by inbound-smtp.us-east-1.amazonaws.com with SMTP id d6iitobk75ur44p8kdnnp7g2n800 for recipient@example.com; Fri, 11 Sep 2015 20:32:33 +0000 (UTC)" + }, + { + "name": "DKIM-Signature", + "value": "v=1; a=rsa-sha256; q=dns/txt; c=relaxed/simple; s=ug7nbtf4gccmlpwj322ax3p6ow6yfsug; d=amazonses.com; t=1442003552; h=From:To:Subject:MIME-Version:Content-Type:Content-Transfer-Encoding:Date:Message-ID:Feedback-ID; bh=DWr3IOmYWoXCA9ARqGC/UaODfghffiwFNRIb2Mckyt4=; b=p4ukUDSFqhqiub+zPR0DW1kp7oJZakrzupr6LBe6sUuvqpBkig56UzUwc29rFbJF hlX3Ov7DeYVNoN38stqwsF8ivcajXpQsXRC1cW9z8x875J041rClAjV7EGbLmudVpPX 4hHst1XPyX5wmgdHIhmUuh8oZKpVqGi6bHGzzf7g=" + }, + { + "name": "From", + "value": "sender@example.com" + }, + { + "name": "To", + "value": "recipient@example.com" + }, + { + "name": "Subject", + "value": "Example subject" + }, + { + "name": "MIME-Version", + "value": "1.0" + }, + { + "name": "Content-Type", + "value": "text/plain; charset=UTF-8" + }, + { + "name": "Content-Transfer-Encoding", + "value": "7bit" + }, + { + "name": "Date", + "value": "Fri, 11 Sep 2015 20:32:32 +0000" + }, + { + "name": "Message-ID", + "value": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>" + }, + { + "name": "X-SES-Outgoing", + "value": "2015.09.11-54.240.9.183" + }, + { + "name": "Feedback-ID", + "value": "1.us-east-1.Krv2FKpFdWV+KUYw3Qd6wcpPJ4Sv/pOPpEPSHn2u2o4=:AmazonSES" + } + ], + "commonHeaders": { + "returnPath": "0000014fbe1c09cf-7cb9f704-7531-4e53-89a1-5fa9744f5eb6-000000@amazonses.com", + "from": [ + "sender@example.com" + ], + "date": "Fri, 11 Sep 2015 20:32:32 +0000", + "to": [ + "recipient@example.com" + ], + "messageId": "<61967230-7A45-4A9D-BEC9-87CBCF2211C9@example.com>", + "subject": "Example subject" + } + } + }, + "eventSource": "aws:ses" + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event-obj.json b/lambda-events/src/fixtures/example-sns-event-obj.json new file mode 100644 index 00000000..c3144618 --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event-obj.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "{\"foo\":\"Hello world!\",\"bar\":123}", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event-pascal-case.json b/lambda-events/src/fixtures/example-sns-event-pascal-case.json new file mode 100644 index 00000000..ec1b125e --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event-pascal-case.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "Hello\r\nworld!", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertUrl" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeUrl" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sns-event.json b/lambda-events/src/fixtures/example-sns-event.json new file mode 100644 index 00000000..091aa579 --- /dev/null +++ b/lambda-events/src/fixtures/example-sns-event.json @@ -0,0 +1,22 @@ +{ + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "Type" : "Notification", + "MessageId" : "82833b5c-8d5d-56d0-b0e1-7511f8253eb8", + "TopicArn" : "arn:aws:sns:us-east-1:246796806071:snsNetTest", + "Subject" : "Greetings", + "Message" : "Hello\r\nworld!", + "Timestamp" : "2015-08-18T18:02:32.111Z", + "SignatureVersion" : "1", + "Signature" : "e+khMfZriwAOTkF0OVm3tmdVq9eY6s5Bj6rXZty4B2TYssx7SSSBpvsDCiDuzgeHe++MNsGLDDT+5OpGEFBqCcd/K7iXhofz+KabMEtvM2Ku3aXcFixjOCAY1BF8hH6zU6nKzOy+m7K4UIoVqIOOhqsLWoXNFWgwQseBol1pFQ/MRi9UH84/WGdU8//dH+1/zjLxCud8Lg1vY9Yi/jxMU1HVpZ2JuvzJBdNBFJWc/VYAiw8K1r/J+dxAiLr87P96MgUqyg1wWxYe00HaEXGtjIctCNcd92s3pngOOeGvPYGaTIZEbYhSf2leMYd+CXujUHRqozru5K0Zp+l99fUNTg==", + "SigningCertURL" : "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-d6d679a1d18e95c2f9ffcf11f4f9e198.pem", + "UnsubscribeURL" : "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:246796806071:snsNetTest:228cc6c9-dcd8-4c92-9f3a-77f55176b9e3" + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sqs-batch-response.json b/lambda-events/src/fixtures/example-sqs-batch-response.json new file mode 100644 index 00000000..50cb377b --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-batch-response.json @@ -0,0 +1,10 @@ +{ + "batchItemFailures": [ + { + "itemIdentifier": "id2" + }, + { + "itemIdentifier": "id4" + } + ] +} diff --git a/lambda-events/src/fixtures/example-sqs-event-obj.json b/lambda-events/src/fixtures/example-sqs-event-obj.json new file mode 100644 index 00000000..76428275 --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-event-obj.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "messageId" : "MessageID_1", + "receiptHandle" : "MessageReceiptHandle", + "body" : "{\"a\":\"Test\",\"b\":123}", + "md5OfBody" : "fce0ea8dd236ccb3ed9b37dae260836f", + "md5OfMessageAttributes" : "582c92c5c5b6ac403040a4f3ab3115c9", + "eventSourceARN": "arn:aws:sqs:us-west-2:123456789012:SQSQueue", + "eventSource": "aws:sqs", + "awsRegion": "us-west-2", + "attributes" : { + "ApproximateReceiveCount" : "2", + "SentTimestamp" : "1520621625029", + "SenderId" : "AROAIWPX5BD2BHG722MW4:sender", + "ApproximateFirstReceiveTimestamp" : "1520621634884" + }, + "messageAttributes" : { + "Attribute3" : { + "binaryValue" : "MTEwMA==", + "stringListValues" : ["abc", "123"], + "binaryListValues" : ["MA==", "MQ==", "MA=="], + "dataType" : "Binary" + }, + "Attribute2" : { + "stringValue" : "123", + "stringListValues" : [ ], + "binaryListValues" : ["MQ==", "MA=="], + "dataType" : "Number" + }, + "Attribute1" : { + "stringValue" : "AttributeValue1", + "stringListValues" : [ ], + "binaryListValues" : [ ], + "dataType" : "String" + } + } + } + ] +} + diff --git a/lambda-events/src/fixtures/example-sqs-event.json b/lambda-events/src/fixtures/example-sqs-event.json new file mode 100644 index 00000000..732b65c1 --- /dev/null +++ b/lambda-events/src/fixtures/example-sqs-event.json @@ -0,0 +1,41 @@ +{ + "Records": [ + { + "messageId" : "MessageID_1", + "receiptHandle" : "MessageReceiptHandle", + "body" : "Message Body", + "md5OfBody" : "fce0ea8dd236ccb3ed9b37dae260836f", + "md5OfMessageAttributes" : "582c92c5c5b6ac403040a4f3ab3115c9", + "eventSourceARN": "arn:aws:sqs:us-west-2:123456789012:SQSQueue", + "eventSource": "aws:sqs", + "awsRegion": "us-west-2", + "attributes" : { + "ApproximateReceiveCount" : "2", + "SentTimestamp" : "1520621625029", + "SenderId" : "AROAIWPX5BD2BHG722MW4:sender", + "ApproximateFirstReceiveTimestamp" : "1520621634884" + }, + "messageAttributes" : { + "Attribute3" : { + "binaryValue" : "MTEwMA==", + "stringListValues" : ["abc", "123"], + "binaryListValues" : ["MA==", "MQ==", "MA=="], + "dataType" : "Binary" + }, + "Attribute2" : { + "stringValue" : "123", + "stringListValues" : [ ], + "binaryListValues" : ["MQ==", "MA=="], + "dataType" : "Number" + }, + "Attribute1" : { + "stringValue" : "AttributeValue1", + "stringListValues" : [ ], + "binaryListValues" : [ ], + "dataType" : "String" + } + } + } + ] +} + diff --git a/lambda-events/src/lib.rs b/lambda-events/src/lib.rs new file mode 100644 index 00000000..fa6cce05 --- /dev/null +++ b/lambda-events/src/lib.rs @@ -0,0 +1,175 @@ +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; + +mod custom_serde; +/// Encodings used in AWS Lambda json event values. +pub mod encodings; +pub mod time_window; + +/// AWS Lambda event definitions. +pub mod event; + +/// AWS Lambda event definitions for activemq. +#[cfg(feature = "activemq")] +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; + +/// AWS Lambda event definitions for appsync. +#[cfg(feature = "appsync")] +pub use event::appsync; + +/// AWS Lambda event definitions for autoscaling. +#[cfg(feature = "autoscaling")] +pub use event::autoscaling; + +/// AWS Lambda event definitions for chime_bot. +#[cfg(feature = "chime_bot")] +pub use event::chime_bot; + +/// AWS Lambda event definitions for clientvpn. +#[cfg(feature = "clientvpn")] +pub use event::clientvpn; + +/// CloudWatch Events payload +#[cfg(feature = "cloudwatch_events")] +pub use event::cloudwatch_events; + +/// AWS Lambda event definitions for cloudwatch_logs. +#[cfg(feature = "cloudwatch_logs")] +pub use event::cloudwatch_logs; + +/// AWS Lambda event definitions for code_commit. +#[cfg(feature = "code_commit")] +pub use event::code_commit; + +/// AWS Lambda event definitions for codebuild. +#[cfg(feature = "codebuild")] +pub use event::codebuild; + +/// AWS Lambda event definitions for codedeploy. +#[cfg(feature = "codedeploy")] +pub use event::codedeploy; + +/// AWS Lambda event definitions for codepipeline_cloudwatch. +#[cfg(feature = "codepipeline_cloudwatch")] +pub use event::codepipeline_cloudwatch; + +/// AWS Lambda event definitions for codepipeline_job. +#[cfg(feature = "codepipeline_job")] +pub use event::codepipeline_job; + +/// AWS Lambda event definitions for cognito. +#[cfg(feature = "cognito")] +pub use event::cognito; + +/// AWS Lambda event definitions for config. +#[cfg(feature = "config")] +pub use event::config; + +/// AWS Lambda event definitions for connect. +#[cfg(feature = "connect")] +pub use event::connect; + +/// AWS Lambda event definitions for dynamodb. +#[cfg(feature = "dynamodb")] +pub use event::dynamodb; + +/// AWS Lambda event definitions for ecr_scan. +#[cfg(feature = "ecr_scan")] +pub use event::ecr_scan; + +/// AWS Lambda event definitions for firehose. +#[cfg(feature = "firehose")] +pub use event::firehose; + +/// AWS Lambda event definitions for iam. +#[cfg(feature = "iam")] +pub use event::iam; + +/// AWS Lambda event definitions for iot. +#[cfg(feature = "iot")] +pub use event::iot; + +/// AWS Lambda event definitions for iot_1_click. +#[cfg(feature = "iot_1_click")] +pub use event::iot_1_click; + +/// AWS Lambda event definitions for iot_button. +#[cfg(feature = "iot_button")] +pub use event::iot_button; + +/// AWS Lambda event definitions for iot_deprecated. +#[cfg(feature = "iot_deprecated")] +pub use event::iot_deprecated; + +/// AWS Lambda event definitions for kafka. +#[cfg(feature = "kafka")] +pub use event::kafka; + +/// AWS Lambda event definitions for kinesis. +#[cfg(feature = "kinesis")] +pub use event::kinesis; + +/// AWS Lambda event definitions for kinesis_analytics. +#[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")] +pub use event::lambda_function_urls; + +/// AWS Lambda event definitions for lex. +#[cfg(feature = "lex")] +pub use event::lex; + +/// AWS Lambda event definitions for rabbitmq. +#[cfg(feature = "rabbitmq")] +pub use event::rabbitmq; + +/// AWS Lambda event definitions for s3. +#[cfg(feature = "s3")] +pub use event::s3; + +/// AWS Lambda event definitions for s3_batch_job. +#[cfg(feature = "s3")] +pub use event::s3::batch_job as s3_batch_job; + +/// AWS Lambda event definitions for ses. +#[cfg(feature = "ses")] +pub use event::ses; + +/// AWS Lambda event definitions for SNS. +#[cfg(feature = "sns")] +pub use event::sns; + +/// AWS Lambda event definitions for SQS. +#[cfg(feature = "sqs")] +pub use event::sqs; + +/// AWS Lambda event definitions for streams. +#[cfg(feature = "streams")] +pub use event::streams; diff --git a/lambda-events/src/time_window.rs b/lambda-events/src/time_window.rs new file mode 100644 index 00000000..9418dc8c --- /dev/null +++ b/lambda-events/src/time_window.rs @@ -0,0 +1,81 @@ +use chrono::{DateTime, Utc}; +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 +#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Window { + pub start: DateTime, + pub end: DateTime, +} + +impl Default for Window { + fn default() -> Self { + Window { + start: Utc::now(), + end: Utc::now(), + } + } +} + +/// `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 +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowProperties { + /// Time window for the records in the event. + pub window: Window, + /// State being built up to this invoke in the time window. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub state: HashMap, + /// Shard id of the records + #[serde(default)] + pub shard_id: Option, + /// The event source ARN of the service that generated the event (eg. DynamoDB or Kinesis) + #[serde(default)] + #[serde(rename = "eventSourceARN")] + pub event_source_arn: Option, + /// Set to true for the last invoke of the time window. + /// Subsequent invoke will start a new time window along with a fresh state. + pub is_final_invoke_for_window: bool, + /// Set to true if window is terminated prematurely. + /// Subsequent invoke will continue the same window with a fresh state. + pub is_window_terminated_early: bool, +} + +/// `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 +#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct TimeWindowEventResponseProperties { + /// State being built up to this invoke in the time window. + #[serde(deserialize_with = "deserialize_lambda_map")] + #[serde(default)] + pub state: HashMap, +} + +#[cfg(test)] +mod test { + use super::*; + + extern crate serde_json; + + #[test] + fn test_window_deserializer() { + let v = serde_json::json!({ + "start": "2020-12-09T07:04:00Z", + "end": "2020-12-09T07:06:00Z", + }); + + let parsed: Window = serde_json::from_value(v).unwrap(); + assert_eq!("2020-12-09T07:04:00+00:00", &parsed.start.to_rfc3339()); + assert_eq!("2020-12-09T07:06:00+00:00", &parsed.end.to_rfc3339()); + } +} diff --git a/lambda-http/Cargo.toml b/lambda-http/Cargo.toml index 73d05675..289aec17 100644 --- a/lambda-http/Cargo.toml +++ b/lambda-http/Cargo.toml @@ -39,7 +39,8 @@ url = "2.2" percent-encoding = "2.2" [dependencies.aws_lambda_events] -version = "0.8.3" +path = "../lambda-events" +version = "0.9.0" default-features = false features = ["alb", "apigw"] diff --git a/lambda-http/src/request.rs b/lambda-http/src/request.rs index 2711a343..fe9b5fb1 100644 --- a/lambda-http/src/request.rs +++ b/lambda-http/src/request.rs @@ -192,7 +192,7 @@ fn into_proxy_request(ag: ApiGatewayProxyRequest) -> http::Request { headers.extend(ag.headers); update_xray_trace_id_header(&mut headers); - let base64 = ag.is_base64_encoded.unwrap_or_default(); + let base64 = ag.is_base64_encoded; let mut req = builder .body( ag.body @@ -306,7 +306,7 @@ fn into_websocket_request(ag: ApiGatewayWebsocketProxyRequest) -> http::Request< headers.extend(ag.headers); update_xray_trace_id_header(&mut headers); - let base64 = ag.is_base64_encoded.unwrap_or_default(); + let base64 = ag.is_base64_encoded; let mut req = builder .body( ag.body diff --git a/lambda-http/src/response.rs b/lambda-http/src/response.rs index 5a5f3e9f..1a2ede5c 100644 --- a/lambda-http/src/response.rs +++ b/lambda-http/src/response.rs @@ -65,8 +65,8 @@ impl LambdaResponse { #[cfg(feature = "apigw_rest")] RequestOrigin::ApiGatewayV1 => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), headers: headers.clone(), multi_value_headers: headers, }), @@ -86,8 +86,8 @@ impl LambdaResponse { LambdaResponse::ApiGatewayV2(ApiGatewayV2httpResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), cookies, headers: headers.clone(), multi_value_headers: headers, @@ -109,8 +109,8 @@ impl LambdaResponse { #[cfg(feature = "apigw_websockets")] RequestOrigin::WebSocket => LambdaResponse::ApiGatewayV1(ApiGatewayProxyResponse { body, + is_base64_encoded, status_code: status_code as i64, - is_base64_encoded: Some(is_base64_encoded), headers: headers.clone(), multi_value_headers: headers, }),